Compare commits

...

46 Commits

Author SHA1 Message Date
Alex Tran
e04d25d8f5 chore(mobile): better second to first assets shown 2024-06-12 16:58:58 -05:00
Alex
c642150b85 chore(mobile): post release task (#10228) 2024-06-12 14:17:58 -05:00
Alex The Bot
a8a7d29891 Version v1.106.3 2024-06-12 18:26:10 +00:00
Alex
67e98ed313 fix(mobile): video player not updating state (#10220)
* fix(mobile): video player not updating state

* unused code
2024-06-12 12:43:01 -05:00
renovate[bot]
47ef48e3c2 chore(deps): update base-image to v20240611 (major) (#10118)
chore(deps): update base-image to v20240611

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-12 12:51:46 -04:00
waclaw66
376feadb76 fix(web): missing svelte translations (#10199)
* fix(web): missing svelte translations

* fixes

* format fix

* translation keys fix

* "merge" key fix

* Update web/src/lib/components/shared-components/side-bar/more-information-albums.svelte

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* Update web/src/lib/i18n/en.json

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* suggestion fix

* trash pluralization

* video+photo count fix

* format fix

* unused removal

* translation key fix

* duplicate key removal

* format fix

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
2024-06-12 17:37:46 +01:00
Jason Rasmussen
3d82005797 fix: no floats (replace with doubles) (#10218)
* fix: no floats (replace with doubles)

* Update server/src/utils/misc.ts

Co-authored-by: Zack Pollard <zackpollard@ymail.com>

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-12 17:36:24 +01:00
Weblate (bot)
10aa00af21 chore(web): update translations (#10216)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translation: Immich/immich

Co-authored-by: Mario <17320863+myanesp@users.noreply.github.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: opl- <jakub.trzy@op.pl>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2024-06-12 17:35:04 +01:00
Zack Pollard
1f8bdcdce7 chore: renovate shouldn't update mobile native dependencies (#10217) 2024-06-12 17:00:54 +01:00
Jason Rasmussen
98ebfc22f8 chore: translations from mobile (#10214) 2024-06-12 15:47:51 +01:00
Weblate (bot)
032b99fe93 chore(web): update translations (#10203)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
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/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
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/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Alexandr Zhytnyk <oper.kh@gmail.com>
Co-authored-by: Amadeous <am4d3ous@users.noreply.hosted.weblate.org>
Co-authored-by: Beniamin Iorga <beniiorga@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Kim <shnukoms@users.noreply.hosted.weblate.org>
Co-authored-by: Maximilian Waidelich <44324946+maxwai@users.noreply.github.com>
Co-authored-by: Maximilian Waidelich <maximilian.waidelich@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Thomas <thomas.ceccato.02@gmail.com>
Co-authored-by: Yves ANDOLFATTO <register@yves.aleeas.com>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: clementdelestre <clementdelestre@gmail.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: mgabor <mgabor@users.noreply.hosted.weblate.org>
Co-authored-by: opl- <jakub.trzy@op.pl>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: wariw <wariwpl@gmail.com>
Co-authored-by: Владислав Потаенко <vipotaenko02@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
2024-06-12 15:38:18 +01:00
Zack Pollard
07156135c2 fix(server): double counting cores when processor name includes the word "processor" (#10211) 2024-06-12 13:49:20 +00:00
Michel Heusschen
9dbf5db72e fix(server): checkExistingAssets (#10192)
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-12 08:48:44 -05:00
Daniel Heppner
52170423be feat(web): select all duplicates (#10189)
* feat(web): select all duplicates

Allows users to select or deselect all duplicate photos when removing duplicates

* styling

* chore(web): add more translations to duplicates page

* color

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-12 13:01:55 +00:00
Zack Pollard
ae095baad3 fix(server): only run healthchecks when api worker is running on immich-server (#10204)
fix: only run healthchecks when api worker is running on immich-server
2024-06-12 12:44:30 +01:00
Michel Heusschen
f99f289f74 fix(web): small translation issues + remove unused (#10200)
* fix(web): small translation issues + remove unused

* more unused keys

* formatting

* fix(web): incorrectly used translations

* fix and remove unused translations

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-12 12:13:10 +01:00
Alex
476eea44df chore(web): remove thumbnail usage for places card (#10142)
* chore(web): remove thumbnail usage for places

* remove href attribute from Thumbnail

* linting
2024-06-12 11:12:58 +00:00
Jason Rasmussen
e84657192c refactor: config caching (#10168) 2024-06-12 11:07:35 +00:00
Mert
5dda5d93f5 chore(docs): remove microservices from hwa docs (#10188)
remove microservices from hwa docs
2024-06-12 11:57:40 +01:00
Michel Heusschen
6260caf649 fix(web): multi file upload in albums (#10190) 2024-06-12 11:57:11 +01:00
Michel Heusschen
9e5c52b7b7 chore(web): more translations for user settings and admin pages (#10161)
* chore(web): more translations for user settings and admin pages

* JobSettings translations

* feedback

* missed one

* feedback
2024-06-12 11:54:40 +01:00
Weblate (bot)
0e1311e3d3 chore(web): update translations (#10152)
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/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
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/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
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/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/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
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: 94tiger <94tiger@naver.com>
Co-authored-by: Adrian <adrian.hundseth@gmail.com>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: AngelaDMerkel <personal@caduffy.com>
Co-authored-by: Anton <ajp_anton@hotmail.com>
Co-authored-by: Beniamin Iorga <beniiorga@gmail.com>
Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Flowake <weblate.cx6on@passmail.net>
Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Jakub <jakubula.jm@gmail.com>
Co-authored-by: Jan <account@thebraker.net>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: Jason Dean Lessenich <jasonlessenich@gmail.com>
Co-authored-by: Joachim Klahr <joachim@klahr.se>
Co-authored-by: Joseph <josephlegrand33+hosted.weblate.org@gmail.com>
Co-authored-by: Julien Deveaux <julien.deveaux@hotmail.com>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Kim <shnukoms@users.noreply.hosted.weblate.org>
Co-authored-by: Kyle Park <mysky3056@gmail.com>
Co-authored-by: League2EB <info@league2eb.me>
Co-authored-by: Londoneye02 <jcdelcaz@gmail.com>
Co-authored-by: Luca Kröger <l.kroeger01@gmail.com>
Co-authored-by: Manic87 <nicolas@familie-mach.net>
Co-authored-by: Marcos Besteiro López (MarcosBL) <marcosbl@gmail.com>
Co-authored-by: MeisterEder286 <walbrun.johann@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Miko-Matias Grönvall <matias.gronvall@gmail.com>
Co-authored-by: MozPri <primoz.arh@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Ole Morten Didriksen <code@oledid.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Peter Suba <peter.suba@gmail.com>
Co-authored-by: Pheggas <petko252@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Ptsa Daniel <ptsa1987@gmail.com>
Co-authored-by: RWDai <869759838@qq.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Simmer Lajos <weblate.linguini033@passinbox.com>
Co-authored-by: SisyphusMD <guardian.note2892@fastmail.com>
Co-authored-by: Smiehoo <github@pocz.net>
Co-authored-by: Thomas <thomas.ceccato.02@gmail.com>
Co-authored-by: Tomas Babej <web+weblate@tbabej.com>
Co-authored-by: Tomek <tjomek@gmail.com>
Co-authored-by: VB <Victor2B@protonmail.com>
Co-authored-by: Vojtěch Bargl <bargl.vojtech@gmail.com>
Co-authored-by: YFrendo <yann.frendo@live.fr>
Co-authored-by: Yves ANDOLFATTO <register@yves.aleeas.com>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: biglate <bigtech+weblate@aleeas.com>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: clementdelestre <clementdelestre@gmail.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: ferrets <ferrets@live.cn>
Co-authored-by: frauhottelmann <frauhottelmann@gmail.com>
Co-authored-by: gilo <giantlolli@proton.me>
Co-authored-by: grgergo <gergo_g@proton.me>
Co-authored-by: guillezcurra <guillezcurra@gmail.com>
Co-authored-by: ingria <codefuhrer@gmail.com>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: myurar1a <sirometroid1235@outlook.jp>
Co-authored-by: sephrat <florian.dupret@gmail.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: Кирилл Москатов <kirillmoskatov@gmail.com>
2024-06-12 11:52:33 +01:00
Stephen Smith
216cca4383 fix(server): exiftool largefilesupport only set for the first call (#10167)
* Revert "feat(server): enable exiftool largefilesupport (#9894)"

This reverts commit afa10ebcb2.

* feat(server): enable exiftool largefilesupport by passing options to read
2024-06-12 05:43:38 -05:00
Mert
cdc98de848 fix(server): increase pixel limit for thumbnail generation (#10181)
disable input limit
2024-06-11 22:11:03 -04:00
Mert
126cbeabe8 feat(server): add av1 support for vaapi (#10180)
add av1
2024-06-12 00:24:06 +00:00
Mert
2e0c6f6fff fix: postgres health check reporting any db without checksums as unhealthy (#10178)
handle disabled checksumming
2024-06-12 00:18:24 +00:00
Alex The Bot
81790ab166 Version v1.106.2 2024-06-11 19:09:13 +00:00
Alejandro Armas
69b948f3d0 fix(mobile): Motion Photos stopping music (#10151)
Add videoPlayer opt to prevent motionPhotos pausing music
2024-06-11 11:14:49 -05:00
Weblate (bot)
4b2ed28b1a chore: update translations (#10141)
chore(web): update translations

Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
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/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Alex van den Hoogen <alex3305@gmail.com>
Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Jordi Masip <jordi@masip.cat>
Co-authored-by: Joseph <josephlegrand33+hosted.weblate.org@gmail.com>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Manic87 <nicolas@familie-mach.net>
Co-authored-by: Marcos Besteiro López (MarcosBL) <marcosbl@gmail.com>
Co-authored-by: Mario <shopping.uncate@aleeas.com>
Co-authored-by: Michał Kulik <michal.kulik91@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Zack Pollard <zack@futo.org>
Co-authored-by: upsetdog <upsetdog@proton.me>
Co-authored-by: Łukasz Kierepka <lukasz_kierepka@hotmail.com>
2024-06-11 17:06:53 +01:00
Michel Heusschen
b8e6ae65b1 fix(web): backwards asset navigation in GalleryViewer (#10132)
* fix(web): backwards asset navigation in GalleryViewer

* fix ctrl/cmd click

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-11 15:27:18 +00:00
renovate[bot]
36bdbf93a7 fix(deps): update machine-learning (#10099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-11 11:24:10 -04:00
Alex
3eee6c4dcf fix(web): cannot view image when metadata sharing is turned off for public sharing (#10145)
* fix(web): cannot view image when metadata sharing is turned off for public sharing

* Update web/src/lib/utils/asset-utils.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-11 15:23:48 +00:00
Michel Heusschen
3a3676bc82 fix(server): cache-control header missing from / requests (#10131)
* fix(server): cache-control header missing from / requests

* disable extension fallback
2024-06-11 10:18:52 -05:00
Zack Pollard
34fc572276 chore: update translations (#10140)
* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 81.0% (631 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 81.0% (631 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

---------

Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-11 15:28:03 +01:00
Zack Pollard
ef17c257ef chore: update translations (#10138)
* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Spanish)

Currently translated at 4.2% (33 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Russian)

Currently translated at 0.5% (4 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Czech)

Currently translated at 2.6% (21 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Russian)

Currently translated at 0.7% (6 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Dutch)

Currently translated at 5.9% (46 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Italian)

Currently translated at 4.4% (35 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Polish)

Currently translated at 0.6% (5 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 6.0% (47 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Russian)

Currently translated at 2.0% (16 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 80.2% (625 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 80.2% (625 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Vietnamese)

Currently translated at 0.5% (4 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

---------

Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Luca Kröger <l.kroeger01@gmail.com>
Co-authored-by: Héctor Martínez Juste <hectorzin@hotmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Stefan Gries <stefan@gries.nrw>
Co-authored-by: Bouchet Mateo <mateo.bouchet+hosted.weblate.org@mhaz42.fr>
Co-authored-by: Alessandro Saglia <webslate.eskimo0977@bear-d.me>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Quan <weiyideai520@hotmail.com>
Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-11 15:16:23 +01:00
Weblate (bot)
4c69cb89d7 chore: update translations (#10125)
* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Spanish)

Currently translated at 4.2% (33 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Russian)

Currently translated at 0.5% (4 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Czech)

Currently translated at 2.6% (21 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Russian)

Currently translated at 0.7% (6 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Dutch)

Currently translated at 5.9% (46 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Italian)

Currently translated at 4.4% (35 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Polish)

Currently translated at 0.6% (5 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 6.0% (47 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

---------

Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Luca Kröger <l.kroeger01@gmail.com>
Co-authored-by: Héctor Martínez Juste <hectorzin@hotmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Stefan Gries <stefan@gries.nrw>
Co-authored-by: Bouchet Mateo <mateo.bouchet+hosted.weblate.org@mhaz42.fr>
Co-authored-by: Alessandro Saglia <webslate.eskimo0977@bear-d.me>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Quan <weiyideai520@hotmail.com>
Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
2024-06-11 13:42:57 +00:00
Jason Rasmussen
735455508c feat(cli): auto-release (#10127) 2024-06-11 08:33:36 -05:00
Alex
eba166a2f1 fix(web): cannot click on explore place (#10121) 2024-06-11 08:32:39 -05:00
Jason Rasmussen
8cf8a2cb35 chore(cli): prepare release (#10124) 2024-06-11 12:16:49 +00:00
Zack Pollard
1767ed2192 chore(web): enable prettier json key sorting recursively (#10120) 2024-06-11 12:52:20 +01:00
Zack Pollard
3c15dae341 docs: fix archive script labels and change to variable to nextVersion (#10119) 2024-06-11 12:37:20 +01:00
Zack Pollard
8568c2e8b9 docs: add archived docs back to v1.100.0 (#10116)
chore: add archived docs back to v1.100.0
2024-06-11 12:36:59 +01:00
Alex
d558ea819a fix(web): cannot perform duplication actions as normal user (#10115)
* fix(web): cannot perform duplication actions as normal user

* use immich dialog
2024-06-11 11:30:42 +00:00
Alex
60701d131e chore(mobile): post release pump (#10114) 2024-06-11 06:26:52 -05:00
Alex
04808f8b5c fix(mobile): warning message not resetting when changing server URL (#10112) 2024-06-11 10:48:49 +00:00
Zack Pollard
8a866297f7 docs: fix archive version url to include v prefix (#10111)
* docs: fix archive version url to include v prefix

* docs: fix archived versions to add missing v to 1.106.1
2024-06-11 05:43:39 -05:00
180 changed files with 7995 additions and 6311 deletions

View File

@@ -1,16 +1,17 @@
name: CLI Build
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "cli/**"
- ".github/workflows/cli.yml"
- 'cli/**'
- '.github/workflows/cli.yml'
pull_request:
branches: [main]
paths:
- "cli/**"
- ".github/workflows/cli.yml"
- 'cli/**'
- '.github/workflows/cli.yml'
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -32,8 +33,8 @@ jobs:
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
with:
node-version: "20.x"
registry-url: "https://registry.npmjs.org"
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Prepare SDK
run: npm ci --prefix ../open-api/typescript-sdk/
- name: Build SDK
@@ -41,7 +42,7 @@ jobs:
- run: npm ci
- run: npm run build
- run: npm publish
if: ${{ github.event_name == 'workflow_dispatch' }}
if: ${{ github.event_name == 'release' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -83,15 +84,15 @@ jobs:
images: |
name=ghcr.io/${{ github.repository_owner }}/immich-cli
tags: |
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image
uses: docker/build-push-action@v5.4.0
with:
file: cli/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name == 'workflow_dispatch' }}
push: ${{ github.event_name == 'release' }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.metadata.outputs.tags }}

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"fast-glob": "^3.3.2",
@@ -47,7 +47,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.106.1",
"version": "1.106.3",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.3",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",

View File

@@ -103,7 +103,7 @@ services:
ports:
- 5432:5432
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m

View File

@@ -61,7 +61,7 @@ services:
ports:
- 5432:5432
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m

View File

@@ -59,7 +59,7 @@ services:
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m

View File

@@ -43,7 +43,7 @@ if [ -n "${quota:-}" ] && [ -n "${period:-}" ]; then
cpus=1
fi
else
cpus=$(grep -c processor /proc/cpuinfo)
cpus=$(grep -c ^processor /proc/cpuinfo)
fi
echo "$cpus"

View File

@@ -60,17 +60,17 @@ For RKMPP to work:
#### Basic Setup
1. If you do not already have it, download the latest [`hwaccel.transcoding.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-microservices`, uncomment the `extends` section and change `cpu` to the appropriate backend.
2. In the `docker-compose.yml` under `immich-server`, uncomment the `extends` section and change `cpu` to the appropriate backend.
- For VAAPI on WSL2, be sure to use `vaapi-wsl` rather than `vaapi`
3. Redeploy the `immich-microservices` container with these updated settings.
3. Redeploy the `immich-server` container with these updated settings.
4. In the Admin page under `Video transcoding settings`, change the hardware acceleration setting to the appropriate option and save.
5. (Optional) If using a compatible backend, you may enable hardware decoding for optimal performance.
#### Single Compose File
Some platforms, including Unraid and Portainer, do not support multiple Compose files as of writing. As an alternative, you can "inline" the relevant contents of the [`hwaccel.transcoding.yml`][hw-file] file into the `immich-microservices` service directly.
Some platforms, including Unraid and Portainer, do not support multiple Compose files as of writing. As an alternative, you can "inline" the relevant contents of the [`hwaccel.transcoding.yml`][hw-file] file into the `immich-server` service directly.
For example, the `qsv` section in this file is:
@@ -79,21 +79,22 @@ devices:
- /dev/dri:/dev/dri
```
You can add this to the `immich-microservices` service instead of extending from `hwaccel.transcoding.yml`:
You can add this to the `immich-server` service instead of extending from `hwaccel.transcoding.yml`:
```yaml
immich-microservices:
container_name: immich_microservices
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# Note the lack of an `extends` section
devices:
- /dev/dri:/dev/dri
command: ['start.sh', 'microservices']
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 2283:3001
depends_on:
- redis
- database

View File

@@ -1,10 +1,58 @@
[
{
"label": "1.106.1",
"url": "https://1.106.1.archive.immich.app"
"label": "v1.106.3",
"url": "https://v1.106.3.archive.immich.app"
},
{
"label": "v1.106.2",
"url": "https://v1.106.2.archive.immich.app"
},
{
"label": "v1.106.1",
"url": "https://v1.106.1.archive.immich.app"
},
{
"label": "v1.105.1",
"url": "https://v1.105.1.archive.immich.app/"
},
{
"label": "v1.105.0",
"url": "https://v1.105.0.archive.immich.app/"
},
{
"label": "v1.104.0",
"url": "https://v1.104.0.archive.immich.app/"
},
{
"label": "v1.103.1",
"url": "https://v1.103.1.archive.immich.app/"
},
{
"label": "v1.103.0",
"url": "https://v1.103.0.archive.immich.app/"
},
{
"label": "v1.102.3",
"url": "https://v1.102.3.archive.immich.app/"
},
{
"label": "v1.102.2",
"url": "https://v1.102.2.archive.immich.app/"
},
{
"label": "v1.102.1",
"url": "https://v1.102.1.archive.immich.app/"
},
{
"label": "v1.102.0",
"url": "https://v1.102.0.archive.immich.app/"
},
{
"label": "v1.101.0",
"url": "https://v1.101.0.archive.immich.app/"
},
{
"label": "v1.100.0",
"url": "https://v1.100.0.archive.immich.app/"
}
]

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.106.1",
"version": "1.106.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.106.1",
"version": "1.106.3",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@immich/cli": "file:../cli",
@@ -39,7 +39,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.3",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -81,7 +81,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.106.1",
"version": "1.106.3",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.106.1",
"version": "1.106.3",
"description": "",
"main": "index.js",
"type": "module",

View File

@@ -1148,4 +1148,29 @@ describe('/asset', () => {
expect(video.checksum).toStrictEqual(checksum);
});
});
describe('POST /assets/exist', () => {
it('ignores invalid deviceAssetIds', async () => {
const response = await utils.checkExistingAssets(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetIds: ['invalid', 'INVALID'],
});
expect(response.existingIds).toHaveLength(0);
});
it('returns the IDs of existing assets', async () => {
await utils.createAsset(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetId: 'test-asset-0',
});
const response = await utils.checkExistingAssets(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetIds: ['test-asset-0'],
});
expect(response.existingIds).toEqual(['test-asset-0']);
});
});
});

View File

@@ -3,6 +3,7 @@ import {
AssetMediaCreateDto,
AssetMediaResponseDto,
AssetResponseDto,
CheckExistingAssetsDto,
CreateAlbumDto,
CreateLibraryDto,
MetadataSearchDto,
@@ -10,6 +11,7 @@ import {
SharedLinkCreateDto,
UserAdminCreateDto,
ValidateLibraryDto,
checkExistingAssets,
createAlbum,
createApiKey,
createLibrary,
@@ -374,6 +376,9 @@ export const utils = {
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
checkExistingAssets: (accessToken: string, checkExistingAssetsDto: CheckExistingAssetsDto) =>
checkExistingAssets({ checkExistingAssetsDto }, { headers: asBearerAuth(accessToken) }),
metadataSearch: async (accessToken: string, dto: MetadataSearchDto) => {
return searchMetadata({ metadataSearchDto: dto }, { headers: asBearerAuth(accessToken) });
},

View File

@@ -1236,13 +1236,13 @@ socks = ["socksio (==1.*)"]
[[package]]
name = "huggingface-hub"
version = "0.23.2"
version = "0.23.3"
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "huggingface_hub-0.23.2-py3-none-any.whl", hash = "sha256:48727a16e704d409c4bb5913613308499664f22a99743435dc3a13b23c485827"},
{file = "huggingface_hub-0.23.2.tar.gz", hash = "sha256:f6829b62d5fdecb452a76fdbec620cba4c1573655a8d710c1df71735fd9edbd2"},
{file = "huggingface_hub-0.23.3-py3-none-any.whl", hash = "sha256:22222c41223f1b7c209ae5511d2d82907325a0e3cdbce5f66949d43c598ff3bc"},
{file = "huggingface_hub-0.23.3.tar.gz", hash = "sha256:1a1118a0b3dea3bab6c325d71be16f5ffe441d32f3ac7c348d6875911b694b5b"},
]
[package.dependencies]
@@ -2054,18 +2054,18 @@ sympy = "*"
[[package]]
name = "opencv-python-headless"
version = "4.9.0.80"
version = "4.10.0.82"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
{file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"},
{file = "opencv-python-headless-4.10.0.82.tar.gz", hash = "sha256:de9e742c1b9540816fbd115b0b03841d41ed0c65566b0d7a5371f98b131b7e6d"},
{file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a09ed50ba21cc5bf5d436cb0e784ad09c692d6b1d1454252772f6c8f2c7b4088"},
{file = "opencv_python_headless-4.10.0.82-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:977a5fd21e1fe0d3d2134887db4441f8725abeae95150126302f31fcd9f548fa"},
{file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db4ec6755838b0be12510bfc9ffb014779c612418f11f4f7e6f505c36124a3aa"},
{file = "opencv_python_headless-4.10.0.82-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a37fa5276967ecf6eb297295b16b28b7a2eb3b568ca0ee469fb1a5954de298"},
{file = "opencv_python_headless-4.10.0.82-cp37-abi3-win32.whl", hash = "sha256:94736e9b322d13db4768fd35588ad5e8995e78e207263076bfbee18aac835ad5"},
{file = "opencv_python_headless-4.10.0.82-cp37-abi3-win_amd64.whl", hash = "sha256:c1822fa23d1641c0249ed5eb906f4c385f7959ff1bd601a776d56b0c18914af4"},
]
[package.dependencies]
@@ -2438,13 +2438,13 @@ files = [
[[package]]
name = "pytest"
version = "8.2.1"
version = "8.2.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-8.2.1-py3-none-any.whl", hash = "sha256:faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1"},
{file = "pytest-8.2.1.tar.gz", hash = "sha256:5046e5b46d8e4cac199c373041f26be56fdb81eb4e67dc11d4e10811fc3408fd"},
{file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"},
{file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"},
]
[package.dependencies]
@@ -2799,28 +2799,28 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.4.7"
version = "0.4.8"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.4.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e089371c67892a73b6bb1525608e89a2aca1b77b5440acf7a71dda5dac958f9e"},
{file = "ruff-0.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:10f973d521d910e5f9c72ab27e409e839089f955be8a4c8826601a6323a89753"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59c3d110970001dfa494bcd95478e62286c751126dfb15c3c46e7915fc49694f"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa9773c6c00f4958f73b317bc0fd125295110c3776089f6ef318f4b775f0abe4"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07fc80bbb61e42b3b23b10fda6a2a0f5a067f810180a3760c5ef1b456c21b9db"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fa4dafe3fe66d90e2e2b63fa1591dd6e3f090ca2128daa0be33db894e6c18648"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7c0083febdec17571455903b184a10026603a1de078428ba155e7ce9358c5f6"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad1b20e66a44057c326168437d680a2166c177c939346b19c0d6b08a62a37589"},
{file = "ruff-0.4.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf5d818553add7511c38b05532d94a407f499d1a76ebb0cad0374e32bc67202"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50e9651578b629baec3d1513b2534de0ac7ed7753e1382272b8d609997e27e83"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8874a9df7766cb956b218a0a239e0a5d23d9e843e4da1e113ae1d27ee420877a"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b9de9a6e49f7d529decd09381c0860c3f82fa0b0ea00ea78409b785d2308a567"},
{file = "ruff-0.4.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:13a1768b0691619822ae6d446132dbdfd568b700ecd3652b20d4e8bc1e498f78"},
{file = "ruff-0.4.7-py3-none-win32.whl", hash = "sha256:769e5a51df61e07e887b81e6f039e7ed3573316ab7dd9f635c5afaa310e4030e"},
{file = "ruff-0.4.7-py3-none-win_amd64.whl", hash = "sha256:9e3ab684ad403a9ed1226894c32c3ab9c2e0718440f6f50c7c5829932bc9e054"},
{file = "ruff-0.4.7-py3-none-win_arm64.whl", hash = "sha256:10f2204b9a613988e3484194c2c9e96a22079206b22b787605c255f130db5ed7"},
{file = "ruff-0.4.7.tar.gz", hash = "sha256:2331d2b051dc77a289a653fcc6a42cce357087c5975738157cd966590b18b5e1"},
{file = "ruff-0.4.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7663a6d78f6adb0eab270fa9cf1ff2d28618ca3a652b60f2a234d92b9ec89066"},
{file = "ruff-0.4.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eeceb78da8afb6de0ddada93112869852d04f1cd0f6b80fe464fd4e35c330913"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aad360893e92486662ef3be0a339c5ca3c1b109e0134fcd37d534d4be9fb8de3"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:284c2e3f3396fb05f5f803c9fffb53ebbe09a3ebe7dda2929ed8d73ded736deb"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7354f921e3fbe04d2a62d46707e569f9315e1a613307f7311a935743c51a764"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:72584676164e15a68a15778fd1b17c28a519e7a0622161eb2debdcdabdc71883"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9678d5c9b43315f323af2233a04d747409d1e3aa6789620083a82d1066a35199"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704977a658131651a22b5ebeb28b717ef42ac6ee3b11e91dc87b633b5d83142b"},
{file = "ruff-0.4.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05f8d6f0c3cce5026cecd83b7a143dcad503045857bc49662f736437380ad45"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6ea874950daca5697309d976c9afba830d3bf0ed66887481d6bca1673fc5b66a"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fc95aac2943ddf360376be9aa3107c8cf9640083940a8c5bd824be692d2216dc"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:384154a1c3f4bf537bac69f33720957ee49ac8d484bfc91720cc94172026ceed"},
{file = "ruff-0.4.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e9d5ce97cacc99878aa0d084c626a15cd21e6b3d53fd6f9112b7fc485918e1fa"},
{file = "ruff-0.4.8-py3-none-win32.whl", hash = "sha256:6d795d7639212c2dfd01991259460101c22aabf420d9b943f153ab9d9706e6a9"},
{file = "ruff-0.4.8-py3-none-win_amd64.whl", hash = "sha256:e14a3a095d07560a9d6769a72f781d73259655919d9b396c650fc98a8157555d"},
{file = "ruff-0.4.8-py3-none-win_arm64.whl", hash = "sha256:14019a06dbe29b608f6b7cbcec300e3170a8d86efaddb7b23405cb7f7dcaf780"},
{file = "ruff-0.4.8.tar.gz", hash = "sha256:16d717b1d57b2e2fd68bd0bf80fb43931b79d05a7131aa477d66fc40fbd86268"},
]
[[package]]

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.106.1"
version = "1.106.3"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -1,8 +1,8 @@
#! /usr/bin/env node
const { readFileSync, writeFileSync } = require('node:fs');
const lastVersion = process.argv[2];
if (!lastVersion) {
const nextVersion = process.argv[2];
if (!nextVersion) {
console.log('Usage: archive-version.js <version>');
process.exit(1);
}
@@ -10,7 +10,7 @@ if (!lastVersion) {
const filename = './docs/static/archived-versions.json';
const oldVersions = JSON.parse(readFileSync(filename));
const newVersions = [
{ label: lastVersion, url: `https://${lastVersion}.archive.immich.app` },
{ label: `v${nextVersion}`, url: `https://v${nextVersion}.archive.immich.app` },
...oldVersions,
];

View File

@@ -66,10 +66,12 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
npm --prefix server run build
make open-api
npm --prefix open-api/typescript-sdk version "$SERVER_PUMP"
npm --prefix web version "$SERVER_PUMP"
npm --prefix e2e version "$SERVER_PUMP"
npm --prefix web i --package-lock-only
# TODO use $SERVER_PUMP once we pass 2.2.x
npm --prefix cli version patch
npm --prefix cli i --package-lock-only
npm --prefix web version "$SERVER_PUMP"
npm --prefix web i --package-lock-only
npm --prefix e2e version "$SERVER_PUMP"
npm --prefix e2e i --package-lock-only
poetry --directory machine-learning version "$SERVER_PUMP"
fi

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 141,
"android.injected.version.name" => "1.106.1",
"android.injected.version.code" => 143,
"android.injected.version.name" => "1.106.3",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000374">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000381">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="84.292464">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="52.832426">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="33.336934">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="27.616558">
</testcase>

View File

@@ -383,7 +383,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 157;
CURRENT_PROJECT_VERSION = 160;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -525,7 +525,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 157;
CURRENT_PROJECT_VERSION = 160;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -553,7 +553,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 157;
CURRENT_PROJECT_VERSION = 160;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -58,11 +58,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.105.0</string>
<string>1.106.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>157</string>
<string>160</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.106.1"
version_number: "1.106.3"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.020864">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000491">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.917777">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="39.414297">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.283943">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="32.521647">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.944748">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.511733">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="215.971639">
<testcase classname="fastlane.lanes" name="4: build_app" time="202.628277">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="76.674601">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="73.861852">
</testcase>

View File

@@ -75,9 +75,7 @@ class VideoViewerPage extends HookConsumerWidget {
// Also sets the error if there is an error in the playback
void updateVideoPlayback() {
final videoPlayback = VideoPlaybackValue.fromController(controller);
if (!loopVideo) {
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
}
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
final state = videoPlayback.state;
// Enable the WakeLock while the video is playing
@@ -110,7 +108,9 @@ class VideoViewerPage extends HookConsumerWidget {
}
// Subscribes to listener
controller.addListener(updateVideoPlayback);
Future.microtask(() {
controller.addListener(updateVideoPlayback);
});
return () {
// Removes listener when we dispose
controller.removeListener(updateVideoPlayback);

View File

@@ -50,8 +50,19 @@ class AssetNotifier extends StateNotifier<bool> {
await clearAssetsAndAlbums(_db);
log.info("Manual refresh requested, cleared assets and albums from db");
}
final bool changedUsers = await _userService.refreshUsers();
final assetCount = await _db.assets.count();
if (assetCount == 0) {
debugPrint("First sync, refreshing all assets");
await _assetService.refreshRemoteAssets(firstSync: true);
debugPrint("First sync, DONE refreshing all assets");
}
debugPrint("First sync, CONTINUE refreshing all assets");
final bool newRemote = await _assetService.refreshRemoteAssets();
final bool newLocal = await _albumService.refreshDeviceAlbums();
debugPrint(
"changedUsers: $changedUsers, newRemote: $newRemote, newLocal: $newLocal",

View File

@@ -31,6 +31,9 @@ Future<VideoPlayerController> videoPlayerController(
controller = VideoPlayerController.networkUrl(
url,
httpHeaders: {"x-immich-user-token": accessToken},
videoPlayerOptions: asset.livePhotoVideoId != null
? VideoPlayerOptions(mixWithOthers: true)
: VideoPlayerOptions(mixWithOthers: false),
);
}

View File

@@ -7,7 +7,7 @@ part of 'video_player_controller_provider.dart';
// **************************************************************************
String _$videoPlayerControllerHash() =>
r'40b31f7b1a73fab84c311b0f06bedf5322143cd9';
r'642469a44287188a7c301f5cad3df3e23c84d85c';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -93,4 +93,18 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
pause: !state.pause,
);
}
void restart() {
state = VideoPlaybackControls(
position: 0,
mute: state.mute,
pause: true,
);
state = VideoPlaybackControls(
position: 0,
mute: state.mute,
pause: false,
);
}
}

View File

@@ -6,7 +6,7 @@ part of 'map_state.provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$mapStateNotifierHash() => r'87a8623f726d438d115d5a15609c71372726ee2f';
String _$mapStateNotifierHash() => r'31fafe17aa85c48379a22ed3db3cc94af59ce5b8';
/// See also [MapStateNotifier].
@ProviderFor(MapStateNotifier)

View File

@@ -43,7 +43,7 @@ class AssetService {
/// Checks the server for updated assets and updates the local database if
/// required. Returns `true` if there were any changes.
Future<bool> refreshRemoteAssets() async {
Future<bool> refreshRemoteAssets({bool firstSync = false}) async {
final syncedUserIds = await _db.eTags.where().idProperty().findAll();
final List<User> syncedUsers = syncedUserIds.isEmpty
? []
@@ -57,6 +57,7 @@ class AssetService {
getChangedAssets: _getRemoteAssetChanges,
loadAssets: _getRemoteAssets,
refreshUsers: _userService.getUsersFromServer,
firstSync: firstSync,
);
debugPrint("refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms");
return changes;
@@ -97,8 +98,12 @@ class AssetService {
}
/// Returns `null` if the server state did not change, else list of assets
Future<List<Asset>?> _getRemoteAssets(User user, DateTime until) async {
const int chunkSize = 10000;
Future<List<Asset>?> _getRemoteAssets(
User user,
DateTime until,
bool firstSync,
) async {
int chunkSize = firstSync ? 1000 : 10000;
try {
final List<Asset> allAssets = [];
String? lastId;
@@ -120,6 +125,11 @@ class AssetService {
allAssets.addAll(assets.map(Asset.remote));
if (assets.length != chunkSize) break;
lastId = assets.last.id;
if (firstSync) {
// first sync only loads the first chunk
break;
}
}
return allAssets;
} catch (error, stack) {

View File

@@ -46,14 +46,18 @@ class SyncService {
List<User> users,
DateTime since,
) getChangedAssets,
required FutureOr<List<Asset>?> Function(User user, DateTime until)
loadAssets,
required FutureOr<List<Asset>?> Function(
User user,
DateTime until,
bool firstSync,
) loadAssets,
required FutureOr<List<User>?> Function() refreshUsers,
required bool firstSync,
}) =>
_lock.run(
() async =>
await _syncRemoteAssetChanges(users, getChangedAssets) ??
await _syncRemoteAssetsFull(refreshUsers, loadAssets),
await _syncRemoteAssetsFull(refreshUsers, loadAssets, firstSync),
);
/// Syncs remote albums to the database
@@ -212,7 +216,9 @@ class SyncService {
/// Syncs assets by loading and comparing all assets from the server.
Future<bool> _syncRemoteAssetsFull(
FutureOr<List<User>?> Function() refreshUsers,
FutureOr<List<Asset>?> Function(User user, DateTime until) loadAssets,
FutureOr<List<Asset>?> Function(User user, DateTime until, bool firstSync)
loadAssets,
bool firstSync,
) async {
final serverUsers = await refreshUsers();
if (serverUsers == null) {
@@ -228,17 +234,19 @@ class SyncService {
.findAll();
bool changes = false;
for (User u in users) {
changes |= await _syncRemoteAssetsForUser(u, loadAssets);
changes |= await _syncRemoteAssetsForUser(u, loadAssets, firstSync);
}
return changes;
}
Future<bool> _syncRemoteAssetsForUser(
User user,
FutureOr<List<Asset>?> Function(User user, DateTime until) loadAssets,
FutureOr<List<Asset>?> Function(User user, DateTime until, bool firstSync)
loadAssets,
bool firstSync,
) async {
final DateTime now = DateTime.now().toUtc();
final List<Asset>? remote = await loadAssets(user, now);
final List<Asset>? remote = await loadAssets(user, now, firstSync);
if (remote == null) {
return false;
}

View File

@@ -64,6 +64,8 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
final state = ref.read(videoPlaybackValueProvider).state;
if (state == VideoPlaybackState.playing) {
ref.read(videoPlayerControlsProvider.notifier).pause();
} else if (state == VideoPlaybackState.completed) {
ref.read(videoPlayerControlsProvider.notifier).restart();
} else {
ref.read(videoPlayerControlsProvider.notifier).play();
}

View File

@@ -54,7 +54,7 @@ class LoginForm extends HookConsumerWidget {
duration: const Duration(seconds: 60),
)..repeat();
final serverInfo = ref.watch(serverInfoProvider);
final warningMessage = useState<String>('');
final warningMessage = useState<String?>(null);
final ValueNotifier<String?> serverEndpoint = useState<String?>(null);
@@ -67,16 +67,12 @@ class LoginForm extends HookConsumerWidget {
final serverMajorVersion = serverInfo.serverVersion.major;
final serverMinorVersion = serverInfo.serverVersion.minor;
final message = getVersionCompatibilityMessage(
warningMessage.value = getVersionCompatibilityMessage(
appMajorVersion,
appMinorVersion,
serverMajorVersion,
serverMinorVersion,
);
if (message != null) {
warningMessage.value = message;
}
} catch (error) {
warningMessage.value = 'Error checking version compatibility';
}
@@ -345,7 +341,7 @@ class LoginForm extends HookConsumerWidget {
buildVersionCompatWarning() {
checkVersionMismatch();
if (warningMessage.value.isEmpty) {
if (warningMessage.value == null) {
return const SizedBox.shrink();
}
@@ -363,7 +359,7 @@ class LoginForm extends HookConsumerWidget {
),
),
child: Text(
warningMessage.value,
warningMessage.value!,
textAlign: TextAlign.center,
),
),

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.106.1
- API version: 1.106.3
- Generator version: 7.5.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -53,7 +53,7 @@ class DuplicateDetectionConfig {
return DuplicateDetectionConfig(
enabled: mapValueOfType<bool>(json, r'enabled')!,
maxDistance: mapValueOfType<double>(json, r'maxDistance')!,
maxDistance: (mapValueOfType<num>(json, r'maxDistance')!).toDouble(),
);
}
return null;

View File

@@ -74,9 +74,9 @@ class FacialRecognitionConfig {
return FacialRecognitionConfig(
enabled: mapValueOfType<bool>(json, r'enabled')!,
maxDistance: mapValueOfType<double>(json, r'maxDistance')!,
maxDistance: (mapValueOfType<num>(json, r'maxDistance')!).toDouble(),
minFaces: mapValueOfType<int>(json, r'minFaces')!,
minScore: mapValueOfType<double>(json, r'minScore')!,
minScore: (mapValueOfType<num>(json, r'minScore')!).toDouble(),
modelName: mapValueOfType<String>(json, r'modelName')!,
);
}

View File

@@ -84,7 +84,7 @@ class ServerStorageResponseDto {
diskAvailableRaw: mapValueOfType<int>(json, r'diskAvailableRaw')!,
diskSize: mapValueOfType<String>(json, r'diskSize')!,
diskSizeRaw: mapValueOfType<int>(json, r'diskSizeRaw')!,
diskUsagePercentage: mapValueOfType<double>(json, r'diskUsagePercentage')!,
diskUsagePercentage: (mapValueOfType<num>(json, r'diskUsagePercentage')!).toDouble(),
diskUse: mapValueOfType<String>(json, r'diskUse')!,
diskUseRaw: mapValueOfType<int>(json, r'diskUseRaw')!,
);

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 1.106.1+141
version: 1.106.3+143
environment:
sdk: '>=3.3.0 <4.0.0'

View File

@@ -78,8 +78,9 @@ void main() {
final bool c1 = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
loadAssets: (u, d, firstSync) => remoteAssets,
refreshUsers: () => [owner],
firstSync: false,
);
expect(c1, isFalse);
expect(db.assets.countSync(), 5);
@@ -99,8 +100,9 @@ void main() {
final bool c1 = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
loadAssets: (u, d, firstSync) => remoteAssets,
refreshUsers: () => [owner],
firstSync: false,
);
expect(c1, isTrue);
expect(db.assets.countSync(), 7);
@@ -120,16 +122,18 @@ void main() {
final bool c1 = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
loadAssets: (u, d, firstSync) => remoteAssets,
refreshUsers: () => [owner],
firstSync: false,
);
expect(c1, isTrue);
expect(db.assets.countSync(), 8);
final bool c2 = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
loadAssets: (u, d, firstSync) => remoteAssets,
refreshUsers: () => [owner],
firstSync: false,
);
expect(c2, isFalse);
expect(db.assets.countSync(), 8);
@@ -137,8 +141,9 @@ void main() {
final bool c3 = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
loadAssets: (u, d, firstSync) => remoteAssets,
refreshUsers: () => [owner],
firstSync: false,
);
expect(c3, isTrue);
expect(db.assets.countSync(), 7);
@@ -147,8 +152,9 @@ void main() {
final bool c4 = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: _failDiff,
loadAssets: (u, d) => remoteAssets,
loadAssets: (u, d, firstSync) => remoteAssets,
refreshUsers: () => [owner],
firstSync: false,
);
expect(c4, isTrue);
expect(db.assets.countSync(), 9);
@@ -166,8 +172,9 @@ void main() {
final bool c = await s.syncRemoteAssetsToDb(
users: [owner],
getChangedAssets: (user, since) async => (toUpsert, toDelete),
loadAssets: (user, date) => throw Exception(),
loadAssets: (user, date, firstSync) => throw Exception(),
refreshUsers: () => throw Exception(),
firstSync: false,
);
expect(c, isTrue);
expect(db.assets.countSync(), 6);

View File

@@ -6735,7 +6735,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.106.1",
"version": "1.106.3",
"contact": {}
},
"tags": [],
@@ -8146,7 +8146,7 @@
"type": "boolean"
},
"maxDistance": {
"format": "float",
"format": "double",
"maximum": 0.1,
"minimum": 0.001,
"type": "number"
@@ -8347,7 +8347,7 @@
"type": "boolean"
},
"maxDistance": {
"format": "float",
"format": "double",
"maximum": 2,
"minimum": 0,
"type": "number"
@@ -8357,7 +8357,7 @@
"type": "integer"
},
"minScore": {
"format": "float",
"format": "double",
"maximum": 1,
"minimum": 0,
"type": "number"
@@ -9797,7 +9797,7 @@
"type": "integer"
},
"diskUsagePercentage": {
"format": "float",
"format": "double",
"type": "number"
},
"diskUse": {

View File

@@ -1,12 +1,12 @@
{
"name": "@immich/sdk",
"version": "1.106.1",
"version": "1.106.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/sdk",
"version": "1.106.1",
"version": "1.106.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/sdk",
"version": "1.106.1",
"version": "1.106.3",
"description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module",
"main": "./build/index.js",

View File

@@ -1,6 +1,6 @@
/**
* Immich
* 1.106.1
* 1.106.3
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/

View File

@@ -69,7 +69,7 @@
"schedule": "on tuesday"
}
],
"ignorePaths": ["mobile/openapi/pubspec.yaml"],
"ignorePaths": ["mobile/openapi/pubspec.yaml", "mobile/ios", "mobile/android"],
"ignoreDeps": ["http", "intl"],
"labels": ["dependencies", "renovate"]
}

View File

@@ -1,5 +1,5 @@
# dev build
FROM ghcr.io/immich-app/base-server-dev:20240604@sha256:bb31fafb1e8fcb4338c2f7a8f424da3d9c5cf6dd6bdb266c54477c795dd07819 as dev
FROM ghcr.io/immich-app/base-server-dev:20240611@sha256:2047ec0f857a800675379c65404dfdf4ac02ab7684c759916e3256d3d9566027 as dev
RUN apt-get install --no-install-recommends -yqq tini
WORKDIR /usr/src/app
@@ -41,7 +41,7 @@ RUN npm run build
# prod build
FROM ghcr.io/immich-app/base-server-prod:20240604@sha256:481ea3ee56fb0e130804fec25c124d28477f10f8a01f7d06fb2e3f85c181bbb9
FROM ghcr.io/immich-app/base-server-prod:20240611@sha256:efd32a2af6e7ace8bcea1e94115fe95a971fe1d1fef7e667ff6e77364ce51c46
WORKDIR /usr/src/app
ENV NODE_ENV=production \

View File

@@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.106.1",
"version": "1.106.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.106.1",
"version": "1.106.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@nestjs/bullmq": "^10.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.106.1",
"version": "1.106.3",
"description": "",
"author": "",
"private": true,

View File

@@ -254,7 +254,7 @@ export class StorageCore {
this.logger.warn(`Unable to complete move. File size mismatch: ${newPathSize} !== ${oldPathSize}`);
return false;
}
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: true });
if (assetInfo && config.storageTemplate.hashVerificationEnabled) {
const { checksum } = assetInfo;
const newChecksum = await this.cryptoRepository.hashFile(newPath);

View File

@@ -42,8 +42,8 @@ export class SystemConfigCore {
instance = null;
}
async getConfig(force = false): Promise<SystemConfig> {
if (force || !this.config) {
async getConfig({ withCache }: { withCache: boolean }): Promise<SystemConfig> {
if (!withCache || !this.config) {
const lastUpdated = this.lastUpdated;
await this.asyncLock.acquire(DatabaseLock[DatabaseLock.GetSystemConfig], async () => {
if (lastUpdated === this.lastUpdated) {
@@ -74,13 +74,13 @@ export class SystemConfigCore {
await this.repository.set(SystemMetadataKey.SYSTEM_CONFIG, partialConfig);
const config = await this.getConfig(true);
const config = await this.getConfig({ withCache: false });
this.config$.next(config);
return config;
}
async refreshConfig() {
const newConfig = await this.getConfig(true);
const newConfig = await this.getConfig({ withCache: false });
this.config$.next(newConfig);
}

View File

@@ -21,7 +21,7 @@ export class DuplicateDetectionConfig extends TaskConfig {
@Min(0.001)
@Max(0.1)
@Type(() => Number)
@ApiProperty({ type: 'number', format: 'float' })
@ApiProperty({ type: 'number', format: 'double' })
maxDistance!: number;
}
@@ -30,14 +30,14 @@ export class FacialRecognitionConfig extends ModelConfig {
@Min(0)
@Max(1)
@Type(() => Number)
@ApiProperty({ type: 'number', format: 'float' })
@ApiProperty({ type: 'number', format: 'double' })
minScore!: number;
@IsNumber()
@Min(0)
@Max(2)
@Type(() => Number)
@ApiProperty({ type: 'number', format: 'float' })
@ApiProperty({ type: 'number', format: 'double' })
maxDistance!: number;
@IsNumber()

View File

@@ -21,7 +21,7 @@ export class ServerStorageResponseDto {
@ApiProperty({ type: 'integer', format: 'int64' })
diskAvailableRaw!: number;
@ApiProperty({ type: 'number', format: 'float' })
@ApiProperty({ type: 'number', format: 'double' })
diskUsagePercentage!: number;
}

View File

@@ -156,7 +156,7 @@ export interface IAssetRepository {
getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>;
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
getByAlbumId(pagination: PaginationOptions, albumId: string): Paginated<AssetEntity>;
getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<AssetEntity[]>;
getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<string[]>;
getByUserId(pagination: PaginationOptions, userId: string, options?: AssetSearchOptions): Paginated<AssetEntity>;
getById(
id: string,

View File

@@ -155,8 +155,8 @@ export class AssetRepository implements IAssetRepository {
});
}
getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<AssetEntity[]> {
return this.repository.find({
async getByDeviceIds(ownerId: string, deviceId: string, deviceAssetIds: string[]): Promise<string[]> {
const assets = await this.repository.find({
select: { deviceAssetId: true },
where: {
deviceAssetId: In(deviceAssetIds),
@@ -165,6 +165,8 @@ export class AssetRepository implements IAssetRepository {
},
withDeleted: true,
});
return assets.map((asset) => asset.deviceAssetId);
}
getByUserId(

View File

@@ -45,7 +45,7 @@ export class MediaRepository implements IMediaRepository {
}
async generateThumbnail(input: string | Buffer, output: string, options: ThumbnailOptions): Promise<void> {
const pipeline = sharp(input, { failOn: 'none' })
const pipeline = sharp(input, { failOn: 'none', limitInputPixels: false })
.pipelineColorspace(options.colorspace === Colorspace.SRGB ? 'srgb' : 'rgb16')
.rotate();

View File

@@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DefaultExiftoolArgs, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored';
import { DefaultReadTaskOptions, Tags, exiftool } from 'exiftool-vendored';
import geotz from 'geo-tz';
import { DummyValue, GenerateSql } from 'src/decorators';
import { ExifEntity } from 'src/entities/exif.entity';
@@ -21,23 +21,18 @@ export class MetadataRepository implements IMetadataRepository {
) {
this.logger.setContext(MetadataRepository.name);
}
private exiftool: ExifTool = this.initExiftool();
async teardown() {
await this.exiftool.end();
}
private initExiftool() {
// Enable exiftool LFS to parse metadata for files larger than 2GB.
const exiftoolArgs = ['-api', 'largefilesupport=1', ...DefaultExiftoolArgs];
return new ExifTool({ exiftoolArgs });
await exiftool.end();
}
readTags(path: string): Promise<ImmichTags | null> {
return this.exiftool
return exiftool
.read(path, undefined, {
...DefaultReadTaskOptions,
// Enable exiftool LFS to parse metadata for files larger than 2GB.
optionalArgs: ['-api', 'largefilesupport=1'],
defaultVideosToUTC: true,
backfillTimezones: true,
inferTimezoneFromDatestamps: true,
@@ -53,12 +48,12 @@ export class MetadataRepository implements IMetadataRepository {
}
extractBinaryTag(path: string, tagName: string): Promise<Buffer> {
return this.exiftool.extractBinaryTagToBuffer(tagName, path);
return exiftool.extractBinaryTagToBuffer(tagName, path);
}
async writeTags(path: string, tags: Partial<Tags>): Promise<void> {
try {
await this.exiftool.write(path, tags, ['-overwrite_original']);
await exiftool.write(path, tags, ['-overwrite_original']);
} catch (error) {
this.logger.warn(`Error writing exif data (${path}): ${error}`);
}

View File

@@ -277,14 +277,12 @@ export class AssetMediaService {
auth: AuthDto,
checkExistingAssetsDto: CheckExistingAssetsDto,
): Promise<CheckExistingAssetsResponseDto> {
const assets = await this.assetRepository.getByDeviceIds(
const existingIds = await this.assetRepository.getByDeviceIds(
auth.user.id,
checkExistingAssetsDto.deviceId,
checkExistingAssetsDto.deviceAssetIds,
);
return {
existingIds: assets.map((asset) => asset.id),
};
return { existingIds };
}
async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {

View File

@@ -245,7 +245,7 @@ export class AssetService {
}
async handleAssetDeletionCheck(): Promise<JobStatus> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
const trashedDays = config.trash.enabled ? config.trash.days : 0;
const trashedBefore = DateTime.now()
.minus(Duration.fromObject({ days: trashedDays }))

View File

@@ -77,7 +77,7 @@ export class AuthService {
}
async login(dto: LoginCredentialDto, details: LoginDetails) {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
if (!config.passwordLogin.enabled) {
throw new UnauthorizedException('Password login has been disabled');
}
@@ -174,7 +174,7 @@ export class AuthService {
}
async authorize(dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
if (!config.oauth.enabled) {
throw new BadRequestException('OAuth is not enabled');
}
@@ -190,7 +190,7 @@ export class AuthService {
}
async callback(dto: OAuthCallbackDto, loginDetails: LoginDetails) {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
const profile = await this.getOAuthProfile(config, dto.url);
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
let user = await this.userRepository.getByOAuthId(profile.sub);
@@ -242,7 +242,7 @@ export class AuthService {
}
async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserAdminResponseDto> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
const { sub: oauthId } = await this.getOAuthProfile(config, dto.url);
const duplicate = await this.userRepository.getByOAuthId(oauthId);
if (duplicate && duplicate.id !== auth.user.id) {
@@ -264,7 +264,7 @@ export class AuthService {
return LOGIN_URL;
}
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
if (!config.oauth.enabled) {
return LOGIN_URL;
}

View File

@@ -42,25 +42,25 @@ export class CliService {
}
async disablePasswordLogin(): Promise<void> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
config.passwordLogin.enabled = false;
await this.configCore.updateConfig(config);
}
async enablePasswordLogin(): Promise<void> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
config.passwordLogin.enabled = true;
await this.configCore.updateConfig(config);
}
async disableOAuthLogin(): Promise<void> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
config.oauth.enabled = false;
await this.configCore.updateConfig(config);
}
async enableOAuthLogin(): Promise<void> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
config.oauth.enabled = true;
await this.configCore.updateConfig(config);
}

View File

@@ -43,7 +43,7 @@ export class DuplicateService {
}
async handleQueueSearchDuplicates({ force }: IBaseJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
if (!isDuplicateDetectionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}
@@ -64,7 +64,7 @@ export class DuplicateService {
}
async handleSearchDuplicates({ id }: IEntityJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
if (!isDuplicateDetectionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}

View File

@@ -150,7 +150,7 @@ export class JobService {
}
async init(jobHandlers: Record<JobName, JobHandler>) {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
for (const queueName of Object.values(QueueName)) {
let concurrency = 1;

View File

@@ -67,7 +67,7 @@ export class LibraryService {
}
async init() {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
const { watch, scan } = config.library;

View File

@@ -47,7 +47,7 @@ export class MapService {
}
async getMapStyle(theme: 'light' | 'dark') {
const { map } = await this.configCore.getConfig();
const { map } = await this.configCore.getConfig({ withCache: false });
const styleUrl = theme === 'dark' ? map.darkStyle : map.lightStyle;
if (styleUrl) {

View File

@@ -149,7 +149,7 @@ export class MediaService {
}
async handleAssetMigration({ id }: IEntityJob): Promise<JobStatus> {
const { image } = await this.configCore.getConfig();
const { image } = await this.configCore.getConfig({ withCache: true });
const [asset] = await this.assetRepository.getByIds([id]);
if (!asset) {
return JobStatus.FAILED;
@@ -164,7 +164,7 @@ export class MediaService {
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
const [{ image }, [asset]] = await Promise.all([
this.configCore.getConfig(),
this.configCore.getConfig({ withCache: true }),
this.assetRepository.getByIds([id], { exifInfo: true }),
]);
if (!asset) {
@@ -185,7 +185,7 @@ export class MediaService {
}
private async generateThumbnail(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) {
const { image, ffmpeg } = await this.configCore.getConfig();
const { image, ffmpeg } = await this.configCore.getConfig({ withCache: true });
const size = type === AssetPathType.PREVIEW ? image.previewSize : image.thumbnailSize;
const path = StorageCore.getImagePath(asset, type, format);
this.storageCore.ensureFolders(path);
@@ -237,7 +237,7 @@ export class MediaService {
async handleGenerateThumbnail({ id }: IEntityJob): Promise<JobStatus> {
const [{ image }, [asset]] = await Promise.all([
this.configCore.getConfig(),
this.configCore.getConfig({ withCache: true }),
this.assetRepository.getByIds([id], { exifInfo: true }),
]);
if (!asset) {
@@ -318,7 +318,7 @@ export class MediaService {
return JobStatus.FAILED;
}
const { ffmpeg } = await this.configCore.getConfig();
const { ffmpeg } = await this.configCore.getConfig({ withCache: true });
const target = this.getTranscodeTarget(ffmpeg, mainVideoStream, mainAudioStream);
if (target === TranscodeTarget.NONE) {
if (asset.encodedVideoPath) {

View File

@@ -137,7 +137,7 @@ export class MetadataService {
this.subscription = this.configCore.config$.subscribe(() => handlePromiseError(this.init(), this.logger));
}
const { reverseGeocoding } = await this.configCore.getConfig();
const { reverseGeocoding } = await this.configCore.getConfig({ withCache: false });
const { enabled } = reverseGeocoding;
if (!enabled) {
@@ -333,7 +333,7 @@ export class MetadataService {
private async applyReverseGeocoding(asset: AssetEntity, exifData: ExifEntityWithoutGeocodeAndTypeOrm) {
const { latitude, longitude } = exifData;
const { reverseGeocoding } = await this.configCore.getConfig();
const { reverseGeocoding } = await this.configCore.getConfig({ withCache: true });
if (!reverseGeocoding.enabled || !longitude || !latitude) {
return;
}

View File

@@ -68,7 +68,7 @@ export class NotificationService {
throw new HttpException('Failed to verify SMTP configuration', HttpStatus.BAD_REQUEST, { cause: error });
}
const { server } = await this.configCore.getConfig();
const { server } = await this.configCore.getConfig({ withCache: false });
const { html, text } = this.notificationRepository.renderEmail({
template: EmailTemplate.TEST_EMAIL,
data: {
@@ -94,7 +94,7 @@ export class NotificationService {
return JobStatus.SKIPPED;
}
const { server } = await this.configCore.getConfig();
const { server } = await this.configCore.getConfig({ withCache: true });
const { html, text } = this.notificationRepository.renderEmail({
template: EmailTemplate.WELCOME,
data: {
@@ -137,7 +137,7 @@ export class NotificationService {
const attachment = await this.getAlbumThumbnailAttachment(album);
const { server } = await this.configCore.getConfig();
const { server } = await this.configCore.getConfig({ withCache: false });
const { html, text } = this.notificationRepository.renderEmail({
template: EmailTemplate.ALBUM_INVITE,
data: {
@@ -179,7 +179,7 @@ export class NotificationService {
const recipients = [...album.albumUsers.map((user) => user.user), owner].filter((user) => user.id !== senderId);
const attachment = await this.getAlbumThumbnailAttachment(album);
const { server } = await this.configCore.getConfig();
const { server } = await this.configCore.getConfig({ withCache: false });
for (const recipient of recipients) {
const user = await this.userRepository.get(recipient.id, { withDeleted: false });
@@ -220,7 +220,7 @@ export class NotificationService {
}
async handleSendEmail(data: IEmailJob): Promise<JobStatus> {
const { notifications } = await this.configCore.getConfig();
const { notifications } = await this.configCore.getConfig({ withCache: false });
if (!notifications.smtp.enabled) {
return JobStatus.SKIPPED;
}

View File

@@ -88,7 +88,7 @@ export class PersonService {
}
async getAll(auth: AuthDto, dto: PersonSearchDto): Promise<PeopleResponseDto> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
const people = await this.repository.getAllForUser(auth.user.id, {
minimumFaceCount: machineLearning.facialRecognition.minFaces,
withHidden: dto.withHidden || false,
@@ -282,7 +282,7 @@ export class PersonService {
}
async handleQueueDetectFaces({ force }: IBaseJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}
@@ -313,7 +313,7 @@ export class PersonService {
}
async handleDetectFaces({ id }: IEntityJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}
@@ -371,7 +371,7 @@ export class PersonService {
}
async handleQueueRecognizeFaces({ force }: IBaseJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}
@@ -402,7 +402,7 @@ export class PersonService {
}
async handleRecognizeFaces({ id, deferred }: IDeferrableJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}
@@ -486,7 +486,7 @@ export class PersonService {
}
async handleGeneratePersonThumbnail(data: IEntityJob): Promise<JobStatus> {
const { machineLearning, image } = await this.configCore.getConfig();
const { machineLearning, image } = await this.configCore.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}

View File

@@ -95,7 +95,7 @@ export class SearchService {
}
async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise<SearchResponseDto> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
if (!isSmartSearchEnabled(machineLearning)) {
throw new BadRequestException('Smart search is not enabled');
}

View File

@@ -65,7 +65,7 @@ export class ServerInfoService {
async getFeatures(): Promise<ServerFeaturesDto> {
const { reverseGeocoding, map, machineLearning, trash, oauth, passwordLogin, notifications } =
await this.configCore.getConfig();
await this.configCore.getConfig({ withCache: false });
return {
smartSearch: isSmartSearchEnabled(machineLearning),
@@ -85,12 +85,12 @@ export class ServerInfoService {
}
async getTheme() {
const { theme } = await this.configCore.getConfig();
const { theme } = await this.configCore.getConfig({ withCache: false });
return theme;
}
async getConfig(): Promise<ServerConfigDto> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
const isInitialized = await this.userRepository.hasAdmin();
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.ADMIN_ONBOARDING);

View File

@@ -40,7 +40,7 @@ export class SmartInfoService {
await this.jobRepository.waitForQueueCompletion(QueueName.SMART_SEARCH);
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
await this.databaseRepository.withLock(DatabaseLock.CLIPDimSize, () =>
this.repository.init(machineLearning.clip.modelName),
@@ -50,7 +50,7 @@ export class SmartInfoService {
}
async handleQueueEncodeClip({ force }: IBaseJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
if (!isSmartSearchEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}
@@ -75,7 +75,7 @@ export class SmartInfoService {
}
async handleEncodeClip({ id }: IEntityJob): Promise<JobStatus> {
const { machineLearning } = await this.configCore.getConfig();
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
if (!isSmartSearchEnabled(machineLearning)) {
return JobStatus.SKIPPED;
}

View File

@@ -110,7 +110,7 @@ export class StorageTemplateService {
}
async handleMigrationSingle({ id }: IEntityJob): Promise<JobStatus> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: true });
const storageTemplateEnabled = config.storageTemplate.enabled;
if (!storageTemplateEnabled) {
return JobStatus.SKIPPED;
@@ -140,7 +140,7 @@ export class StorageTemplateService {
async handleMigration(): Promise<JobStatus> {
this.logger.log('Starting storage template migration');
const { storageTemplate } = await this.configCore.getConfig();
const { storageTemplate } = await this.configCore.getConfig({ withCache: true });
const { enabled } = storageTemplate;
if (!enabled) {
this.logger.log('Storage template migration disabled, skipping');

View File

@@ -42,7 +42,7 @@ export class SystemConfigService {
}
async init() {
const config = await this.core.getConfig();
const config = await this.core.getConfig({ withCache: false });
this.config$.next(config);
}
@@ -51,7 +51,7 @@ export class SystemConfigService {
}
async getConfig(): Promise<SystemConfigDto> {
const config = await this.core.getConfig();
const config = await this.core.getConfig({ withCache: false });
return mapConfig(config);
}
@@ -71,7 +71,7 @@ export class SystemConfigService {
throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use');
}
const oldConfig = await this.core.getConfig();
const oldConfig = await this.core.getConfig({ withCache: false });
try {
await this.eventRepository.serverSendAsync(ServerAsyncEvent.CONFIG_VALIDATE, {
@@ -110,7 +110,7 @@ export class SystemConfigService {
}
async getCustomCss(): Promise<string> {
const { theme } = await this.core.getConfig();
const { theme } = await this.core.getConfig({ withCache: false });
return theme.customCss;
}

View File

@@ -128,7 +128,7 @@ export class UserService {
async handleUserDeleteCheck(): Promise<JobStatus> {
const users = await this.userRepository.getDeletedUsers();
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
await this.jobRepository.queueAll(
users.flatMap((user) =>
this.isReadyForDeletion(user, config.user.deleteDelay)
@@ -140,7 +140,7 @@ export class UserService {
}
async handleUserDelete({ id, force }: IEntityJob): Promise<JobStatus> {
const config = await this.configCore.getConfig();
const config = await this.configCore.getConfig({ withCache: false });
const user = await this.userRepository.get(id, { withDeleted: true });
if (!user) {
return JobStatus.FAILED;

View File

@@ -56,7 +56,7 @@ export class VersionService {
return JobStatus.SKIPPED;
}
const { newVersionCheck } = await this.configCore.getConfig();
const { newVersionCheck } = await this.configCore.getConfig({ withCache: true });
if (!newVersionCheck.enabled) {
return JobStatus.SKIPPED;
}

View File

@@ -3,6 +3,10 @@ const port = Number(process.env.IMMICH_PORT) || 3001;
const controller = new AbortController();
const main = async () => {
if (!process.env.IMMICH_WORKERS_INCLUDE?.includes('api')) {
process.exit();
}
const timeout = setTimeout(() => controller.abort(), 2000);
try {
const response = await fetch(`http://localhost:${port}/api/server-info/ping`, {

View File

@@ -842,7 +842,7 @@ export class VAAPIConfig extends BaseHWConfig {
}
getSupportedCodecs() {
return [VideoCodec.H264, VideoCodec.HEVC, VideoCodec.VP9];
return [VideoCodec.H264, VideoCodec.HEVC, VideoCodec.VP9, VideoCodec.AV1];
}
useCQP() {

View File

@@ -6,7 +6,7 @@ import {
SwaggerDocumentOptions,
SwaggerModule,
} from '@nestjs/swagger';
import { SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
import { ReferenceObject, SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface';
import _ from 'lodash';
import { writeFileSync } from 'node:fs';
import path from 'node:path';
@@ -111,6 +111,14 @@ function sortKeys<T>(target: T): T {
export const routeToErrorMessage = (methodName: string) =>
'Failed to ' + methodName.replaceAll(/[A-Z]+/g, (letter) => ` ${letter.toLowerCase()}`);
const isSchema = (schema: string | ReferenceObject | SchemaObject): schema is SchemaObject => {
if (typeof schema === 'string' || '$ref' in schema) {
return false;
}
return true;
};
const patchOpenAPI = (document: OpenAPIObject) => {
document.paths = sortKeys(document.paths);
@@ -119,13 +127,23 @@ const patchOpenAPI = (document: OpenAPIObject) => {
document.components.schemas = sortKeys(schemas);
for (const schema of Object.values(schemas)) {
for (const [schemaName, schema] of Object.entries(schemas)) {
if (schema.properties) {
schema.properties = sortKeys(schema.properties);
}
if (schema.required) {
schema.required = schema.required.sort();
for (const [key, value] of Object.entries(schema.properties)) {
if (typeof value === 'string') {
continue;
}
if (isSchema(value) && value.type === 'number' && value.format === 'float') {
throw new Error(`Invalid number format: ${schemaName}.${key}=float (use double instead). `);
}
}
if (schema.required) {
schema.required = schema.required.sort();
}
}
}
}

View File

@@ -46,6 +46,7 @@ async function bootstrap() {
etag: true,
gzip: true,
brotli: true,
extensions: [],
setHeaders: (res, pathname) => {
if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) {
res.setHeader('cache-control', 'public,max-age=31536000,immutable');

View File

@@ -1,4 +1,5 @@
{
"jsonRecursiveSort": true,
"organizeImportsSkipDestructiveCodeActions": true,
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-svelte", "prettier-plugin-sort-json"],

6
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-web",
"version": "1.106.1",
"version": "1.106.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-web",
"version": "1.106.1",
"version": "1.106.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@immich/sdk": "file:../open-api/typescript-sdk",
@@ -68,7 +68,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.106.1",
"version": "1.106.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "immich-web",
"version": "1.106.1",
"version": "1.106.3",
"license": "GNU Affero General Public License version 3",
"scripts": {
"dev": "vite dev --host 0.0.0.0 --port 3000",

View File

@@ -76,13 +76,10 @@
</div>
{#if forceDelete}
<p class="text-immich-error">
WARNING: This will immediately remove the user and all assets. This cannot be undone and the files cannot be
recovered.
</p>
<p class="text-immich-error">{$t('admin.force_delete_user_warning')}</p>
<p class="immich-form-label text-sm" id="confirm-user-desc">
To confirm, type "{user.email}" below
{$t('admin.confirm_email_below', { values: { email: user.email } })}
</p>
<input

View File

@@ -58,7 +58,7 @@
<Badge color="primary">
<div class="flex flex-row gap-1">
<span class="text-sm">
{jobCounts.failed.toLocaleString($locale)} failed
{$t('admin.jobs_failed', { values: { jobCount: jobCounts.failed.toLocaleString($locale) } })}
</span>
<CircleIconButton
color="primary"
@@ -74,7 +74,7 @@
{#if jobCounts.delayed > 0}
<Badge color="secondary">
<span class="text-sm">
{jobCounts.delayed.toLocaleString($locale)} delayed
{$t('admin.jobs_delayed', { values: { jobCount: jobCounts.delayed.toLocaleString($locale) } })}
</span>
</Badge>
{/if}
@@ -119,12 +119,14 @@
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
>
<Icon path={mdiAlertCircle} size="36" /> DISABLED
<Icon path={mdiAlertCircle} size="36" />
{$t('disabled').toUpperCase()}
</JobTileButton>
{:else if !isIdle}
{#if waitingCount > 0}
<JobTileButton color="gray" on:click={() => dispatch('command', { command: JobCommand.Empty, force: false })}>
<Icon path={mdiClose} size="24" /> CLEAR
<Icon path={mdiClose} size="24" />
{$t('clear').toUpperCase()}
</JobTileButton>
{/if}
{#if queueStatus.isPaused}
@@ -134,14 +136,16 @@
on:click={() => dispatch('command', { command: JobCommand.Resume, force: false })}
>
<!-- size property is not reactive, so have to use width and height -->
<Icon path={mdiFastForward} {size} /> RESUME
<Icon path={mdiFastForward} {size} />
{$t('resume').toUpperCase()}
</JobTileButton>
{:else}
<JobTileButton
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Pause, force: false })}
>
<Icon path={mdiPause} size="24" /> PAUSE
<Icon path={mdiPause} size="24" />
{$t('pause').toUpperCase()}
</JobTileButton>
{/if}
{:else if allowForceCommand}
@@ -161,7 +165,8 @@
color="light-gray"
on:click={() => dispatch('command', { command: JobCommand.Start, force: false })}
>
<Icon path={mdiPlay} size="48" /> START
<Icon path={mdiPlay} size="48" />
{$t('start').toUpperCase()}
</JobTileButton>
{/if}
</div>

View File

@@ -43,7 +43,7 @@
if (dto.force) {
const isConfirmed = await dialogController.show({
id: 'confirm-reprocess-all-faces',
prompt: 'Are you sure you want to reprocess all faces? This will also clear named people.',
prompt: $t('admin.confirm_reprocess_all_faces'),
});
if (isConfirmed) {
@@ -60,23 +60,23 @@
$: jobDetails = <Partial<Record<JobName, JobDetails>>>{
[JobName.ThumbnailGeneration]: {
icon: mdiFileJpgBox,
title: getJobName(JobName.ThumbnailGeneration),
title: $getJobName(JobName.ThumbnailGeneration),
subtitle: $t('admin.thumbnail_generation_job_description'),
},
[JobName.MetadataExtraction]: {
icon: mdiTable,
title: getJobName(JobName.MetadataExtraction),
title: $getJobName(JobName.MetadataExtraction),
subtitle: $t('admin.metadata_extraction_job_description'),
},
[JobName.Library]: {
icon: mdiLibraryShelves,
title: getJobName(JobName.Library),
title: $getJobName(JobName.Library),
subtitle: $t('admin.library_tasks_description'),
allText: $t('all').toUpperCase(),
missingText: $t('refresh').toUpperCase(),
},
[JobName.Sidecar]: {
title: getJobName(JobName.Sidecar),
title: $getJobName(JobName.Sidecar),
icon: mdiFileXmlBox,
subtitle: $t('admin.sidecar_job_description'),
allText: $t('sync').toUpperCase(),
@@ -85,46 +85,44 @@
},
[JobName.SmartSearch]: {
icon: mdiImageSearch,
title: getJobName(JobName.SmartSearch),
title: $getJobName(JobName.SmartSearch),
subtitle: $t('admin.smart_search_job_description'),
disabled: !$featureFlags.smartSearch,
},
[JobName.DuplicateDetection]: {
icon: mdiContentDuplicate,
title: getJobName(JobName.DuplicateDetection),
title: $getJobName(JobName.DuplicateDetection),
subtitle: $t('admin.duplicate_detection_job_description'),
disabled: !$featureFlags.duplicateDetection,
},
[JobName.FaceDetection]: {
icon: mdiFaceRecognition,
title: getJobName(JobName.FaceDetection),
subtitle:
'Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. "All" (re-)processes all assets. "Missing" queues assets that haven\'t been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.',
title: $getJobName(JobName.FaceDetection),
subtitle: $t('admin.face_detection_description'),
handleCommand: handleConfirmCommand,
disabled: !$featureFlags.facialRecognition,
},
[JobName.FacialRecognition]: {
icon: mdiTagFaces,
title: getJobName(JobName.FacialRecognition),
subtitle:
'Group detected faces into people. This step runs after Face Detection is complete. "All" (re-)clusters all faces. "Missing" queues faces that don\'t have a person assigned.',
title: $getJobName(JobName.FacialRecognition),
subtitle: $t('admin.facial_recognition_job_description'),
handleCommand: handleConfirmCommand,
disabled: !$featureFlags.facialRecognition,
},
[JobName.VideoConversion]: {
icon: mdiVideo,
title: getJobName(JobName.VideoConversion),
title: $getJobName(JobName.VideoConversion),
subtitle: $t('admin.video_conversion_job_description'),
},
[JobName.StorageTemplateMigration]: {
icon: mdiFolderMove,
title: getJobName(JobName.StorageTemplateMigration),
title: $getJobName(JobName.StorageTemplateMigration),
allowForceCommand: false,
description: StorageMigrationDescription,
},
[JobName.Migration]: {
icon: mdiFolderMove,
title: getJobName(JobName.Migration),
title: $getJobName(JobName.Migration),
subtitle: $t('admin.migration_job_description'),
allowForceCommand: false,
},
@@ -140,14 +138,14 @@
switch (jobCommand.command) {
case JobCommand.Empty: {
notificationController.show({
message: `Cleared jobs for: ${title}`,
message: $t('admin.cleared_jobs', { values: { job: title } }),
type: NotificationType.Info,
});
break;
}
}
} catch (error) {
handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);
handleError(error, $t('admin.failed_job_command', { values: { command: jobCommand.command, job: title } }));
}
}
</script>

View File

@@ -110,7 +110,7 @@
{#if user.quotaSizeInBytes}
({((user.usage / user.quotaSizeInBytes) * 100).toFixed(0)}%)
{:else}
(Unlimited)
({$t('unlimited')})
{/if}
</span>
</td>

View File

@@ -53,7 +53,7 @@
}
notificationController.show({
message: 'Reset settings to the recent saved settings',
message: $t('admin.reset_settings_to_recent_saved'),
type: NotificationType.Info,
});
};
@@ -64,7 +64,7 @@
}
notificationController.show({
message: $t('reset_settings_to_default'),
message: $t('admin.reset_settings_to_default'),
type: NotificationType.Info,
});
};

View File

@@ -270,7 +270,7 @@
},
{
value: TranscodeHWAccel.Disabled,
text: $t('admin.disabled'),
text: $t('disabled'),
},
]}
isEdited={config.ffmpeg.accel !== savedConfig.ffmpeg.accel}

View File

@@ -9,6 +9,7 @@
import SettingInputField, {
SettingInputFieldType,
} from '$lib/components/shared-components/settings/setting-input-field.svelte';
import { t } from 'svelte-i18n';
export let savedConfig: SystemConfigDto;
export let defaultConfig: SystemConfigDto;
@@ -45,7 +46,7 @@
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
{disabled}
label="{getJobName(jobName)} Concurrency"
label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })}
desc=""
bind:value={config.job[jobName].concurrency}
required={true}
@@ -54,11 +55,11 @@
{:else}
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="{getJobName(jobName)} Concurrency"
label={$t('admin.job_concurrency', { values: { job: $getJobName(jobName) } })}
desc=""
value="1"
disabled={true}
title="This job is not concurrency-safe."
title={$t('admin.job_not_concurrency_safe')}
/>
{/if}
</div>

View File

@@ -55,7 +55,7 @@
const substitutions: Record<string, string> = {
filename: 'IMAGE_56437',
ext: 'jpg',
filetype: $t('img').toUpperCase(),
filetype: 'IMG',
filetypefull: 'IMAGE',
assetId: 'a8312960-e277-447d-b4ea-56717ccba856',
album: $t('album_name'),

View File

@@ -177,7 +177,7 @@
color="opaque"
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}
on:click={() => dispatch('favorite')}
title={asset.isFavorite ? $t('unfavorite') : $t('favorite')}
title={asset.isFavorite ? $t('unfavorite') : $t('to_favorite')}
/>
{/if}
@@ -242,7 +242,7 @@
<MenuOption
on:click={() => onMenuClick('toggleArchive')}
icon={asset.isArchived ? mdiArchiveArrowUpOutline : mdiArchiveArrowDownOutline}
text={asset.isArchived ? $t('unarchive') : $t('archive')}
text={asset.isArchived ? $t('unarchive') : $t('to_archive')}
/>
<MenuOption
icon={mdiUpload}

View File

@@ -732,7 +732,8 @@
? 'bg-transparent border-2 border-white'
: 'bg-gray-700/40'} inline-block hover:bg-transparent"
asset={stackedAsset}
onClick={() => {
onClick={(stackedAsset, event) => {
event.preventDefault();
asset = stackedAsset;
preloadAssets = index + 1 >= $stackAssetsStore.length ? [] : [$stackAssetsStore[index + 1]];
}}
@@ -740,7 +741,6 @@
readonly
thumbnailSize={stackedAsset.id == asset.id ? 65 : 60}
showStackedIcon={false}
isStackSlideshow={true}
/>
{#if stackedAsset.id == asset.id}

View File

@@ -97,7 +97,7 @@
};
const onCopyShortcut = (event: KeyboardEvent) => {
if (window.getSelection()?.type === $t('range')) {
if (window.getSelection()?.type === 'Range') {
return;
}
event.preventDefault();
@@ -112,7 +112,7 @@
]}
/>
{#if imageError}
<div class="h-full flex items-center justify-center">Error loading image</div>
<div class="h-full flex items-center justify-center">{$t('error_loading_image')}</div>
{/if}
<div bind:this={element} class="relative h-full select-none">
<img

View File

@@ -22,7 +22,6 @@
import { fade } from 'svelte/transition';
import ImageThumbnail from './image-thumbnail.svelte';
import VideoThumbnail from './video-thumbnail.svelte';
import { shortcut } from '$lib/actions/shortcut';
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
const dispatch = createEventDispatcher<{
@@ -37,13 +36,11 @@
export let thumbnailHeight: number | undefined = undefined;
export let selected = false;
export let selectionCandidate = false;
export let isMultiSelectState = false;
export let isStackSlideshow = false;
export let disabled = false;
export let readonly = false;
export let showArchiveIcon = false;
export let showStackedIcon = true;
export let onClick: ((asset: AssetResponseDto) => void) | undefined = undefined;
export let onClick: ((asset: AssetResponseDto, event: Event) => void) | undefined = undefined;
let className = '';
export { className as class };
@@ -64,14 +61,6 @@
return [235, 235];
})();
const thumbnailClickedHandler = (e: Event) => {
e.stopPropagation();
e.preventDefault();
if (!disabled) {
onClick?.(asset);
}
};
const onIconClickedHandler = (e: MouseEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -80,12 +69,17 @@
}
};
const handleClick = (e: Event) => {
if (isMultiSelectState) {
onIconClickedHandler(e as MouseEvent);
} else if (isStackSlideshow) {
thumbnailClickedHandler(e);
const handleClick = (e: MouseEvent) => {
if (e.ctrlKey || e.metaKey) {
return;
}
if (selected) {
onIconClickedHandler(e);
return;
}
onClick?.(asset, e);
};
const onMouseEnter = () => {
@@ -110,7 +104,6 @@
on:mouseleave={onMouseLeave}
tabindex={0}
on:click={handleClick}
use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: thumbnailClickedHandler }}
>
{#if intersecting}
<div class="absolute z-20 {className}" style:width="{width}px" style:height="{height}px">

View File

@@ -101,23 +101,23 @@
<ControlAppBar on:close={onClose}>
<svelte:fragment slot="leading">
{#if hasSelection}
Selected {selectedPeople.length}
{$t('selected')} {selectedPeople.length}
{:else}
Merge people
{$t('merge_people')}
{/if}
<div />
</svelte:fragment>
<svelte:fragment slot="trailing">
<Button size={'sm'} disabled={!hasSelection} on:click={handleMerge}>
<Icon path={mdiMerge} size={18} />
<span class="ml-2"> Merge</span></Button
<span class="ml-2">{$t('merge')}</span></Button
>
</svelte:fragment>
</ControlAppBar>
<section class="bg-immich-bg px-[70px] pt-[100px] dark:bg-immich-dark-bg">
<section id="merge-face-selector relative">
<div class="mb-10 h-[200px] place-content-center place-items-center">
<p class="mb-4 text-center uppercase dark:text-white">Choose matching people to merge</p>
<p class="mb-4 text-center uppercase dark:text-white">{$t('choose_matching_people_to_merge')}</p>
<div class="grid grid-flow-col-dense place-content-center place-items-center gap-4">
{#each selectedPeople as person (person.id)}

View File

@@ -31,7 +31,7 @@
};
</script>
<FullScreenModal title="Merge people - {title}" onClose={() => dispatch('close')}>
<FullScreenModal title="{$t('merge_people')} - {title}" onClose={() => dispatch('close')}>
<div class="flex items-center justify-center py-4 md:h-36 md:py-4">
{#if !choosePersonToMerge}
<div class="flex h-20 w-20 items-center px-1 md:h-24 md:w-24 md:px-2">

View File

@@ -225,8 +225,8 @@
curve
shadow
url={selectedPersonToCreate[face.id]}
altText={'New person'}
title={'New person'}
altText={$t('new_person')}
title={$t('new_person')}
widthStyle={thumbnailWidth}
heightStyle={thumbnailWidth}
/>
@@ -309,7 +309,7 @@
<CircleIconButton
color="primary"
icon={mdiMinus}
title="Select new face"
title={$t('select_new_face')}
size="18"
padding="1"
class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"

View File

@@ -54,7 +54,7 @@
<div class="flex items-center">
<CircleIconButton title={$t('close')} icon={mdiClose} on:click={onClose} />
<div class="flex gap-2 items-center">
<p class="ml-2">Show & hide people</p>
<p class="ml-2">{$t('show_and_hide_people')}</p>
<p class="text-sm text-gray-400 dark:text-gray-600">({countTotalPeople.toLocaleString($locale)})</p>
</div>
</div>

View File

@@ -33,8 +33,8 @@
await signUpAdmin({ signUpDto: { email, password, name } });
await goto(AppRoute.AUTH_LOGIN);
} catch (error) {
handleError(error, 'errors.errors.unable_to_create_admin_account');
errorMessage = $t('errors.errors.unable_to_create_admin_account');
handleError(error, 'errors.unable_to_create_admin_account');
errorMessage = $t('errors.unable_to_create_admin_account');
}
}
}

View File

@@ -84,7 +84,9 @@
{#if $featureFlags.email}
<div class="my-4 flex place-items-center justify-between gap-2">
<label class="text-sm dark:text-immich-dark-fg" for="send-welcome-email"> Send welcome email </label>
<label class="text-sm dark:text-immich-dark-fg" for="send-welcome-email">
{$t('admin.send_welcome_email')}
</label>
<Slider id="send-welcome-email" bind:checked={notify} />
</div>
{/if}
@@ -101,7 +103,7 @@
<div class="my-4 flex place-items-center justify-between gap-2">
<label class="text-sm dark:text-immich-dark-fg" for="require-password-change">
Require user to change password on first login
{$t('admin.require_password_change_on_login')}
</label>
<Slider id="require-password-change" bind:checked={shouldChangePassword} />
</div>
@@ -113,9 +115,9 @@
<div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
Quota Size (GiB)
{$t('admin.quota_size_gib')}
{#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
<p class="text-red-400 text-sm">{$t('admin.quota_higher_than_disk_size')}</p>
{/if}
</label>
<input class="immich-form-input" id="quotaSize" type="number" min="0" bind:value={quotaSize} />

View File

@@ -55,7 +55,7 @@
const resetPassword = async () => {
const isConfirmed = await dialogController.show({
id: 'confirm-reset-password',
prompt: `Are you sure you want to reset ${user.name}'s password?`,
prompt: $t('admin.confirm_user_password_reset', { values: { user: user.name } }),
});
if (!isConfirmed) {
@@ -110,13 +110,14 @@
</div>
<div class="my-4 flex flex-col gap-2">
<label class="flex items-center gap-2 immich-form-label" for="quotaSize"
>Quota Size (GiB) {#if quotaSizeWarning}
<p class="text-red-400 text-sm">You set a quota higher than the disk size</p>
<label class="flex items-center gap-2 immich-form-label" for="quotaSize">
{$t('admin.quota_size_gib')}
{#if quotaSizeWarning}
<p class="text-red-400 text-sm">{$t('errors.quota_higher_than_disk_size')}</p>
{/if}</label
>
<input class="immich-form-input" id="quotaSize" name="quotaSize" type="number" min="0" bind:value={quotaSize} />
<p>Note: Enter 0 for unlimited quota</p>
<p>{$t('admin.note_unlimited_quota')}</p>
</div>
<div class="my-4 flex flex-col gap-2">
@@ -130,10 +131,10 @@
/>
<p>
Note: To apply the Storage Label to previously uploaded assets, run the
{$t('admin.note_apply_storage_label_previous_assets')}
<a href={AppRoute.ADMIN_JOBS} class="text-immich-primary dark:text-immich-dark-primary">
Storage Migration Job</a
>
{$t('admin.storage_template_migration_job')}
</a>
</p>
</div>

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