Compare commits

...

326 Commits

Author SHA1 Message Date
Alex The Bot
cc1fecfffd Version v1.75.1 2023-08-26 18:31:14 +00:00
Alex
e02817362c fix(server): initialization search service (#3879) 2023-08-26 13:29:34 -05:00
shalong-tanwen
1b0484fc46 fix(mobile): Widget overflow due to exception on logout (#3869) 2023-08-26 00:06:55 -05:00
Alex The Bot
6fe214a784 Version v1.75.0 2023-08-26 04:44:39 +00:00
Alex
e18a9f84a4 feat(web): slideshow mode (#3813)
* slideshow

slideshow for main screen

Added control buttons

update

close detail panel window sif opened

format

5 seconds

remove unused files

handle video player

format

* fix: restrict slideshow to timeline views

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-25 18:20:45 -05:00
Daniel Dietzler
59bb727636 feat(web, server): Ability to use config file instead of admin UI (#3836)
* implement method to read config file

* getConfig returns config file if present

* return isConfigFile for http requests

* disable elements if config file is used, show message if config file is set, copy existing config to clipboard

* fix allowing partial configuration files

* add new env variable to docs

* fix tests

* minor refactoring, address review

* adapt config type in frontend

* remove unnecessary imports

* move config file reading to system-config repo

* add documentation

* fix code formatting in system settings page

* add validator for config file

* fix formatting in docs

* update generated files

* throw error when trying to update config. e.g. via cli or api

* switch to feature flags for isConfigFile

* refactoring

* refactor: config file

* chore: open api

* feat: always show copy/export buttons

* fix: default flags

* refactor: copy to clipboard

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-25 13:44:52 -04:00
Le_Futuriste
20e0c03b39 feat(web): add link to external map in leaflet popup (#3847)
* feat(web): add link to external map in leaflet popup

Sometimes it's useful to open a geo location to an external map
application to not have to copy the coordinates manually.
Here I put a link to OpenStreetMap because it's what I personally use.
But I known some people would want to use something different. We could
instead link to geohacks (eg. https://geohack.toolforge.org/geohack.php?params=048.861085_N_0002.313158_E_globe:Earth)
or make it a configurable param.

* chore: cleanup

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-25 13:19:49 +00:00
waclaw66
6d1567cf44 smaller album title (#3860) 2023-08-25 14:10:08 +02:00
waclaw66
dc3f53a973 album and face menu dots visible on hover only (#3859) 2023-08-25 06:35:52 -05:00
waclaw66
dad7cf47b4 fix(web): delete album consolidation (#3858) 2023-08-25 13:03:16 +02:00
Mert
165b91b068 feat(ml)!: switch image classification and CLIP models to ONNX (#3809) 2023-08-25 06:28:51 +02:00
Jason Rasmussen
8211afb726 feat(web,server)!: configure machine learning via the UI (#3768) 2023-08-25 06:15:03 +02:00
James58899
2cccef174a fix(mobile): missing conversion to UTC time zone (#3495) 2023-08-25 06:08:19 +02:00
Jason Rasmussen
9bbef4a97b refactor(web): shared link key auth (#3855) 2023-08-25 06:03:28 +02:00
Jason Rasmussen
10c2bda3a9 chore: remove without thumbs (#3529)
* refactor(server): remove withoutThumbs

* chore: open api

* fix: bad merge
2023-08-24 21:45:54 -04:00
Fynn Petersen-Frey
cf9e04c8ec feat(server): asset entity audit (#3824)
* feat(server): audit log

* feedback

* Insert to database

* migration

* test

* controller/repository/service

* test

* module

* feat(server): implement audit endpoint

* directly return changed assets

* add daily cleanup of audit table

* fix tests

* review feedback

* ci

* refactor(server): audit implementation

* chore: open api

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-24 15:28:50 -04:00
Daniele Ricci
d6887117ac chore(web): improve drop shadow on three-dots icon (#3835) 2023-08-23 07:20:50 +02:00
Alex
3b11be2859 fix(web): cannot view publlic shared album (#3829) 2023-08-22 08:05:48 +02:00
Alex
d7f52739e8 fix(web): shared link return 404 (#3791) 2023-08-22 07:22:49 +02:00
waclaw66
71ea46d95e fix(web): merge face thumbnail (#3822) 2023-08-22 04:34:53 +02:00
Daniele Ricci
e2afc43506 Use proper text/drop shadow on tree-dots icon and face name (#3800) 2023-08-20 18:36:31 -05:00
waclaw66
6aed1180e7 fix(web): album list padding (#3790) 2023-08-20 18:30:52 -05:00
Flyot
476b735e3c fix(web): ContextMenu unsatisfying UI behaviors (#3787) 2023-08-20 18:28:25 -05:00
Mert
7ad12c7f33 use camera wb for raw (#3806) 2023-08-20 18:26:01 -05:00
Mert
60729a091a make lazy loading default (#3797) 2023-08-20 18:24:14 -05:00
Alex The Bot
d2bad1d553 Version v1.74.0 2023-08-19 06:09:16 +00:00
Jason Rasmussen
3e31ad51be feat: shared link album time buckets (#3776) 2023-08-18 22:19:42 -05:00
Jason Rasmussen
fbeb4664f7 feat(web): archive from album (#3773) 2023-08-18 17:55:06 -05:00
Steffen Auer
4ee8a30a5a chore(mobile): ios map launch, use OSM as map fallback, use dates as labels (#3772) 2023-08-18 17:53:50 -05:00
martyfuhry
6243bce46c chore(mobile): Bump to Flutter 3.13 (#3767)
* Bump to Flutter 3.13.0

* Updates permission status

* Adds hidden to app livecycle state

* Updates and switches to WakelockPlus

* bump flutter version github action

* mobile test version

* fix format

* video player

* video uri

* ios test

* Update android target sdk requirement to PlayStore

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-18 17:52:40 -05:00
Daniele Ricci
98b72fdb9b feat: set person birth date (web only) (#3721)
* Person birth date (data layer)

* Person birth date (data layer)

* Person birth date (service layer)

* Person birth date (service layer, API)

* Person birth date (service layer, API)

* Person birth date (UI) (wip)

* Person birth date (UI) (wip)

* Person birth date (UI) (wip)

* Person birth date (UI) (wip)

* UI: Use "date of birth" everywhere

* UI: better modal dialog

Similar to the API key modal.

* UI: set date of birth from people page

* Use typed events for modal dispatcher

* Date of birth tests (wip)

* Regenerate API

* Code formatting

* Fix Svelte typing

* Fix Svelte typing

* Fix person model [skip ci]

* Minor refactoring [skip ci]

* Typed event dispatcher [skip ci]

* Refactor typed event dispatcher [skip ci]

* Fix unchanged birthdate check [skip ci]

* Remove unnecessary custom transformer [skip ci]

* PersonUpdate: call search index update job only when needed

* Regenerate API

* Code formatting

* Fix tests

* Fix DTO

* Regenerate API

* chore: verbiage and view mode

* feat: show current age

* test: person e2e

* fix: show name for birth date selection

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-18 16:10:29 -04:00
Jason Rasmussen
5e901e4d21 feat(web,server): run jobs for specific assets (#3712)
* feat(web,server): manually queue asset job

* chore: open api

* chore: tests
2023-08-18 09:31:48 -05:00
Craeckie
66490d5db4 chore: Enable logging, but reduce verboseness of typesense container (#3761)
Co-authored-by: ultrabook <ultrabook>
2023-08-18 09:25:52 -05:00
Jason Rasmussen
2b839088c7 feat(web,server): server features (#3756)
* feat: server features

* chore: open api

* icon size

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-18 04:55:26 +00:00
Alex
28d3d3e679 fix(mobile): invalid range on label builder crash timeline (#3759)
* fix(mobile): invalid date on label builder crash timeline

* actual fix

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-17 23:50:41 -05:00
Alex
2de30e34f4 feat(mobile): Improve album UI and Interactions (#3754)
* fix: outlick editable field does not change edit icon

* fix: unfocus on submit change album name

* styling

* styling

* confirm dialog

* Confirm deletion

* render user

* user avatar with image

* use UserCircleAvatar

* rights

* stlying

* remove/leave options

* styling

* state management

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-17 23:26:12 -05:00
Jason Rasmussen
2ff71b0d27 fix(web): play videos on safari (#3748)
* fix(web): play videos on safari

* autoplay

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-17 13:52:50 -05:00
Jason Rasmussen
cdb45364c3 feat(server): add support for the tif extension (#3743) 2023-08-17 10:27:29 -05:00
Jason Rasmussen
8ba338fbe1 refactor(web): harden video can play method (#3745) 2023-08-17 10:02:12 -05:00
Kevin
ce84f9c755 feat(web): album list options (#3667)
* Album view option for cover or list view

* Dropdown can now receive list of icons to display with selected option

* Formatting

* Use table element with formatting similar to other pages

* Make table rows clickable with hover styling

* Also make row navigateable using keyboard without mouse

* Formatting

* Define DropdownOption interface

* Album view mode type definition for typescript support in if statements

* format

* fix typing

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-17 08:46:39 -05:00
shalong-tanwen
d1e74a28d9 fix(mobile): LivePhoto video not uploaded during manual asset upload (#3732) 2023-08-17 07:29:49 -05:00
martin
78a2a9e666 refactor(web): user-settings (#3700)
* refactor(web): user-settings

* feat: move the logic to the server

* use const

* fix: error 403

* fix: remove console.log
2023-08-16 22:56:06 -05:00
Lucas Eduardo
53f5643994 fix: shebangs (#3643)
Signed-off-by: lucasew <lucas59356@gmail.com>
2023-08-16 22:50:01 -05:00
Daniele Ricci
4ee634766d fix(web): label for attribute (#3731) 2023-08-16 16:09:38 -05:00
Jason Rasmussen
bab739efbd restore: bulk actions (#3730)
* feat: improve bulk isArchive and isFavorite updates

* chore: open api
2023-08-16 15:04:55 -05:00
Daniele Ricci
8568ec838a fix(web): Fix label for attribute (#3726) 2023-08-16 13:27:57 -05:00
Jason Rasmussen
4cbb18aabc feat(web): remove and delete from album (#3725) 2023-08-16 13:25:39 -05:00
Daniele Ricci
3fb60aca4f chore(web): better explain what the thumbnails type are for (#3724) 2023-08-16 13:25:07 -05:00
Skyler Mäntysaari
19bbdebdf7 fix(mobile): Do not show version announcement if user is not admin. (#3703) 2023-08-15 21:12:49 -05:00
martin
bc66b1a556 fix(web): user-management layout (#3704)
* fix: user-management layout

* better user form scrollbar

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-16 01:46:23 +00:00
Jason Rasmussen
4762fd83d4 fix(server): link live photos after metadata extraction finishes (#3702)
* fix(server): link live photos after metadata extraction finishes

* chore: fix test

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-08-15 20:34:57 -05:00
martin
c27c12d975 fix(server): people sorting (#3713) 2023-08-15 19:06:49 -05:00
Jason Rasmussen
0abbd85134 fix(web,server): album share performance (#3698) 2023-08-15 13:34:02 -05:00
Jason Rasmussen
af1f00dff9 chore(server): cleanup (#3699) 2023-08-15 11:05:32 -05:00
Vantao
35b4c9d375 doc: update README_zh_CN.md (#3701) 2023-08-15 16:05:00 +00:00
Sergey Kondrikov
74da15e20d fix(web,server): disable partner's archive access (#3695) 2023-08-15 11:02:38 -05:00
Jason Rasmussen
efc7fdb669 fix(web,server): use POST request to get download info (#3694)
* fix(web,server): use POST request to get download info

* chore: open api
2023-08-15 10:49:32 -05:00
Alex
a75f368d5b chore: post update 2023-08-15 09:42:28 -05:00
Alex The Bot
a3b6095b61 Version v1.73.0 2023-08-15 12:45:48 +00:00
Jason Rasmussen
7ca6f80ed2 fix(server): display insta-360 (#3688) 2023-08-15 03:14:52 +00:00
Russell Tan
f1b8a7ab54 fix(server): Does not assign lat/lon if they are at 0,0 #2991 (#3669)
* fix(server): Does not assign lat/lon if they are at 0,0 #2991

* Adds migration file to fix null island rows

* Removed down migration

* Leave empty down function
2023-08-14 20:37:17 -05:00
Jason Rasmussen
079aa13edb fix(web): use server api media types (#3687) 2023-08-14 20:22:33 -05:00
Alex
67bac9ff59 [Localizely] Translations update (#3682) 2023-08-14 13:16:16 -05:00
Alex
0d80ae3a91 fix(mobile): make user.memoryEnable optional (#3680)
* chore(server): avoid breaking changes

* generate api

* mobile app
2023-08-14 12:52:06 -05:00
shalong-tanwen
b1b215f083 fix(server): sort un-named faces in query (#3662) 2023-08-14 11:09:26 -05:00
BMaster
f55c80eadf doc: how move assets,albums,persons from one account to another (#3652)
* Update FAQ.md

Added an item to document what can be done to move photos, albums and persons from one account to another,

* Update FAQ.md

typo in the numbering

* Update FAQ.md

add an 'advanced operation' warning

* Update FAQ.md

Typos

* chore: format

* chore: cleanup syntax codeblock

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-14 09:50:59 -05:00
abhi-chakrab
c81bb2b70a doc: update bulk-upload.md (#3673)
* Update bulk-upload.md

This PR is to add a small clarification to use the same volumes for read only gallery in the docker command. Since the current docker commands refer to "/import" volume mounts that caused some confusion to me.

* chore: formatting

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-14 09:39:47 -05:00
X
5fa9704a65 fix(mobile): Empty albums don't show title (#3576) 2023-08-13 08:39:46 -05:00
Mert
60d39a7d1f fix(sever): meson flag (#3656) 2023-08-13 08:26:13 -05:00
Mert
13564fbc17 fix(server): build sharp without tiff (#3655) 2023-08-12 21:02:33 -05:00
shalong-tanwen
77a5820c3c fix(mobile): manual asset upload - app state handling + cancel button (#3611)
* feat(mobile): Cancel manual asset upload

* fix(mobile): re-add the missing translation keys

* feat(mobile): show manual upload error in backup page

* refactor: manual upload in-progress count

* fix(mobile): handle app state properly during manual asset upload
2023-08-12 16:02:58 -05:00
Jorge Avila
b790354f9a fix(mobile): update useEffect to run on any change instead of only on lead (#3650)
-- this change is mainly for the AlbumInfoListTile widget
2023-08-12 10:02:24 -05:00
Jason Rasmussen
7948819e0c fix(server): time bucket and where (#3649) 2023-08-11 20:29:42 -05:00
Jason Rasmussen
5cd13227ad feat(web): timeline bucket for albums (4) (#3604)
* feat: server changes for album timeline

* feat(web): album timeline view

* chore: open api

* chore: remove archive action

* fix: favorite for non-owners
2023-08-11 11:00:51 -05:00
bo0tzz
36dc7bd924 Chore: clean up example.env file (#3561) 2023-08-10 14:11:07 -05:00
Jason Rasmussen
6bd7c6c06d feat(web): multi-line description edit input (#3640)
* feat(web): multi-line description edit input

* fix: remove focus border
2023-08-10 18:46:41 +00:00
X
e9b0840f01 fix(mobile): improve hero animation (#3636) 2023-08-10 08:38:49 -05:00
Russell Tan
a8b01dc21a fix(web): show warning if upload completed with errors (#3634) 2023-08-10 08:38:29 -05:00
waclaw66
a815592954 fix(web): fix single photo on a map (#3635) 2023-08-10 08:32:01 -05:00
Alex
f4475549d6 chore(web): better action button consistency (#3633)
Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-09 21:05:52 -05:00
Jason Rasmussen
a6eb227330 feat(web,server): user memory settings (#3628)
* feat(web,server): user preference for time-based memories

* chore: open api

* dev: mobile

* fix: update

* mobile work

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-09 21:01:16 -05:00
Fabian Nagel
343087e2b4 docs(guide): run ML on another machine (#3335)
* Add documentation for hosting the ML container on a different machine

* Revert "Add documentation for hosting the ML container on a different machine"

This reverts commit 11e635eb57.

* Moved to Guides section and removed .env file reference
2023-08-10 01:12:39 +00:00
Russell Tan
66b2ad7939 fix(web): show warning on duplicate uploads #2557 (#3613)
* fix(web): show warning on duplicate uploads #2557

* Prettier fix

* color

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-10 01:11:26 +00:00
shalong-tanwen
57a7103d75 feat(mobile): launch map app from exif info (#3626) 2023-08-09 14:47:49 -05:00
Mert
23b836ffbb fix(server): add video capability for nvenc (#3612) 2023-08-09 14:35:41 -05:00
Alex Tran
e54cf914d7 chore: update readme 2023-08-08 13:19:52 -05:00
Mert
fa57853bd2 fix(ml): clear cache if bad zip file (#3600) 2023-08-08 12:33:13 -05:00
Alex
ddd4ec2d9e feat(web/server): webp thumbnail size configurable (#3598)
* feat(server/web): webp thumbnail size configurable

* update api

* add ui and fix test

* lint

* setting for jpeg size

* feat: coerce to number

* api

* jpeg resolution

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-08 09:39:51 -05:00
Mert
1812e8811b fix(server): compile libraw (#3593) 2023-08-08 05:58:03 -05:00
Mert
1d37d8cac0 feat(server): tone-mapping (#3512)
* added tonemapping

* check for hdr in transcode policy

* merged video thumbnail and transcoding logic

* updated tests

* removed log

* added dashboard option, updated api

* `out_color_matrix` for sdr video thumbs, cleanup

* updated tests & styling

* refactored tonemapping setting

* fixed tests

* formatting

* added tests

* updated api

* set target npl higher for mobius and reinhard

* convert to nv12 before nvenc

* fix test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-07 15:35:25 -05:00
Alex
19da705fcb [Localizely] Translations update (#3589) 2023-08-07 15:35:11 -05:00
Alex The Bot
6efc2ec9be Version v1.72.2 2023-08-07 16:41:17 +00:00
Alex
d0c6c7cb33 chore: update immich cli version (#3586)
* chore: update immich cli version

* update package lock json
2023-08-07 11:39:39 -05:00
Alex The Bot
b3b5f063cf Version v1.72.1 2023-08-07 02:54:24 +00:00
Alex
3731cc4334 fix: build step (#3575)
Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-06 21:52:40 -05:00
Alex
13df619ba9 fix(server): pin openapi genreator patch and regenerate api (#3573)
* fix(server): pin openapi genreator patch and regenerate api

* variable
2023-08-06 15:55:14 -05:00
Alex The Bot
3edb347666 Version v1.72.0 2023-08-06 19:48:25 +00:00
Mert
c73832bd9c refactor(ml): model downloading (#3545)
* download facial recognition models

* download hf models

* simplified logic

* updated `predict` for facial recognition

* ensure download method is called

* fixed repo_id for clip

* fixed download destination

* use st's own `snapshot_download`

* conditional download

* fixed predict method

* check if loaded

* minor fixes

* updated mypy overrides

* added pytest-mock

* updated tests

* updated lock
2023-08-05 21:45:13 -05:00
Jason Rasmussen
2f26a7edae feat(server/web): album description (#3558)
* feat(server): add album description

* chore: open api

* fix: tests

* show and edit description on the web

* fix test

* remove unused code

* type event

* format fix

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-06 02:43:26 +00:00
shalong-tanwen
deaf81e2a4 feat(mobile): Manual asset upload (#3445)
* fix: exclude albums filter in backup provider

* refactor: Separate builder methods for Top Control App Bar buttons

* fix: Show download button only for Remote only assets

* fix(mobile): Force Refresh duration is too low to trigger it consistently

* feat(mobile): Make Buttons dynamic in Home Selection DraggableScrollableSheet

* feat(mobile): Manual Asset upload

* refactor(mobile): Replace _showToast with ImmichToast calls

* refactor(mobile): home_page selectionAssetState handling

* chore(mobile): min and initial size of DraggableScrollState increased

This is to prevent the buttons in the bottom sheet getting clipped behind the 3 way navigation buttons
in the default density of Android devices

* feat(mobile): notifications for manual upload progress

* wording

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-06 02:40:50 +00:00
martin
f1b92718d5 fix(web): broken search-bar during page load (#3548)
* fix: broken search-bar during page load

* fix: prevent race condition between go back and close search bar
2023-08-05 13:05:24 -05:00
Alex
1f64649434 fix(web): asset selection show up on top (#3563)
* fix(web): asset selection show up on top

* fix test
2023-08-05 11:53:48 -05:00
Jason Rasmussen
ff32506c5e feat(web): use time buckets of person detail page (3) (#3557)
* feat: add personId to time bucket endpoints

* chore: open api

* feat(web): time bucket on person detail page
2023-08-05 08:58:52 -05:00
Jason Rasmussen
68b5202730 refactor: auto advance asset viewer (#3554) 2023-08-04 23:26:28 -04:00
Jason Rasmussen
c6abef186c refactor(server,web): time buckets for main timeline, archived, and favorites (1) (#3537)
* refactor: time buckets

* feat(web): use new time bucket api

* feat(web): use asset grid in archive/favorites

* chore: open api

* chore: clean up uuid validation

* refactor(web): move memory lane to photos page

* Update web/src/routes/(user)/archive/+page.svelte

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* fix: hide archived photos on main timeline

* fix: select exif info

---------

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>
2023-08-04 17:07:15 -04:00
Jason Rasmussen
e5bdf671b5 refactor(web): move memory lane to photos page (#3541) 2023-08-03 23:52:15 -05:00
Alex
88e92332ee chore(web): organize prettier trigger order (#3538)
* chore(web): organize prettier trigger order

* fix package.json

* fix package.json
2023-08-03 16:26:48 -05:00
Jason Rasmussen
6da51deb83 fix(web): scrollbar (#3536) 2023-08-03 13:20:41 -05:00
Jason Rasmussen
b44f8d52ee refactor(server): standardize user controller (#3501)
* chore: remove redundant sortint

* chore: standardize user controller

* chore: open api

* fix: web dtos
2023-08-03 13:17:38 -05:00
X
fa03ed7dd7 fix(mobile): "preselectedAssets" parameter not working (#3516)
* fix(mobile): "preselectedAssets" parameter not working

* fix(mobile): create album page "initialAssets" parameter not working
2023-08-03 11:40:10 -05:00
Jason Rasmussen
5617b57b26 refactor(web): asset store (#3528)
* refactor(web): asset store

* chore: remove TODO
2023-08-03 10:44:12 -05:00
Paul Thevenot
01210dceac feat(web): Rounded corners on selected assets (#3524)
* feat(web):  Rounded corners on selected assets

Add rounded corner on selected items and fix circle icon transparent background on selection

* refactor: Ran prettier to fix formatting issues
2023-08-03 09:11:21 -05:00
Jason Rasmussen
e4e049d040 fix(web): leave assets selected after creating a shared link (#3531) 2023-08-03 09:11:02 -05:00
Jason Rasmussen
a405fba3bb fix(server): ffprobe not found (#3532)
* fix(server): ffprobe not found

* chore: remove redundant path export

* chore: remove empty space
2023-08-03 08:54:23 -05:00
Alex
b5844db0c7 chore: add Alex mac mini as runner (#3526)
* test

* test'

:

* test

* test

* test

* test

* test

* test

* test

* test

* fixed indent

* test

* add fallback

* doesn't work nevermind

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2023-08-02 23:27:38 -05:00
Jason Rasmussen
28ab1d4551 refactor(web): asset grid state (#3513)
* refactor(web): asset grid state

* fix: multi-select across time buckets

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-02 20:57:11 -05:00
Alex
050ee91289 chore: drop support armV7 (#3509)
* drop support armv7

* update openapi
2023-08-02 16:40:27 -05:00
Alex Tran
5eb8d7e8b0 update openapi 2023-08-02 16:10:55 -05:00
Jason Rasmussen
5e4403bb2e chore(server): process title (#3519) 2023-08-02 16:03:26 -05:00
martyfuhry
fb6591607f Local merged videos should now play locally instead of loading remote (#3521) 2023-08-02 16:01:30 -05:00
Jorge Avila
1cf3378499 Add Spanish translations of Readme (#3511) 2023-08-02 06:51:08 -05:00
Jason Rasmussen
a336aeb007 fix(server): get remote ip (#3515) 2023-08-02 06:50:42 -05:00
Mert
ee49f470b7 feat(server): transcoding hardware acceleration (#3171)
* added transcode configs for nvenc,qsv and vaapi

* updated dev docker compose

* added software fallback

* working vaapi

* minor fixes and added tests

* updated api

* compile libvips

* move hwaccel settings to `hwaccel.yml`

* changed default dockerfile, moved `readdir` call

* removed unused import

* minor cleanup

* fix for arm build

* added documentation, minor fixes

* added intel driver

* updated docs

styling

* uppercase codec and api names

* formatting

* added tests

* updated docs

* removed semicolons

* added link to `hwaccel.yml`

* added newlines

* added `hwaccel` section to docker-compose.prod.yml

* ensure mesa drivers are installed

* switch to mimalloc for sharp

* moved build version and sha256 to json

* let libmfx set the render device

* possible fix for vp9 on qsv

* updated tests

* formatting

* review suggestions

* semicolon

* moved `LD_PRELOAD` to start script

* switched to jellyfin's ffmpeg package

* fixed dockerfile

* use cqp instead of icq for qsv vp9

* updated dockerfile

* added sha256sum for other platforms

* fixtures
2023-08-01 20:56:10 -05:00
Jason Rasmussen
b9cda59172 refactor(server)!: add/remove album assets (#3109)
* refactor: add/remove album assets

* chore: open api

* feat: remove owned assets from album

* refactor: move to bulk id req/res dto

* chore: open api

* chore: merge main

* dev: mobile work

* fix: adding asset from web not sync with mobile

* remove print statement

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-01 20:29:14 -05:00
Jason Rasmussen
ba71c83948 fix(server): apply timezone to storage template (#3504) 2023-08-01 17:08:07 -05:00
Jason Rasmussen
2835919931 fix: load env files for cli (#3503) 2023-08-01 17:07:52 -05:00
Jason Rasmussen
310fab526d chore(server): auto sort open api spec (#3500)
* chore: recursively sort api keys

* chore: open api
2023-08-01 16:49:18 +00:00
Sergey Kondrikov
690b87e375 fix(web): minor issues (#3499)
* Replace isArchived with isFavorite

* Remove excess AssetSelectControlBar
2023-08-01 10:52:50 -05:00
Jason Rasmussen
e53625b067 test(server): auth e2e (#3492)
* test(server): auth controller e2e test

* test(server): user e2e test

* refactor(server): album e2e

* fix: linting
2023-08-01 10:49:50 -05:00
Jason Rasmussen
9e085c1071 refactor(server): test fixtures (#3491) 2023-07-31 20:28:07 -05:00
Sergey Kondrikov
5f9dfa9493 refactor(web): asset grid stores (#3464)
* Refactor asset grid stores

* Iterate over buckets with for..of loop

* Rebase on top of main branch changes
2023-07-31 20:27:56 -05:00
Jason Rasmussen
13051c1e5a docs: update architecture.md (#3488) 2023-07-31 14:04:44 -05:00
PyKen
a9cd3609dd fix(mobile): Improve vertical swipe detection in gallery viewer (#3486)
* Improve vertical swipe detection in gallery viewer

* Use final
2023-07-31 13:18:40 -05:00
shalong-tanwen
e0a3e5a200 feat(mobile): auto close memories on scrolling beyond first / last memory (#3476)
* feat(mobile): Close on scroll beyond first / last memory

* fix(mobile): close memory only on the last asset
2023-07-31 13:14:17 -05:00
Alex
c587fb1df8 fix(mobile): fix asset order in album from latest to oldest (#3469) 2023-07-31 13:02:29 -05:00
PyKen
51cfe10c28 feat(web/server): Search by panorama photos (#3470)
* Add panorama filter

* Add generated api changes

* Fix naming
2023-07-30 20:31:57 -05:00
David Mládek
bc3f95c57c Fix typo in logo docs (#3475) 2023-07-30 20:28:46 -05:00
martin
e368b9e50b feat: handle escape key and higher wheel zoom ratio (#3471) 2023-07-30 11:03:08 -05:00
Alex
95c75c289c blog: july 2023 (#3466)
* blog: july 2023

* content

* wording

* add truncate

* Update docs/blog/2023/07-29/update.mdx

Co-authored-by: bo0tzz <git@bo0tzz.me>

* Update docs/blog/2023/07-29/update.mdx

Co-authored-by: bo0tzz <git@bo0tzz.me>

* Update docs/blog/2023/07-29/update.mdx

Co-authored-by: bo0tzz <git@bo0tzz.me>

* Update docs/blog/2023/07-29/update.mdx

Co-authored-by: bo0tzz <git@bo0tzz.me>

* Update docs/blog/2023/07-29/update.mdx

Co-authored-by: bo0tzz <git@bo0tzz.me>

* title

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
2023-07-29 21:30:30 -05:00
Jacob Roberts
23d3657ac2 fix the grammar for albumns to be backed up (#3461) 2023-07-29 01:33:48 -05:00
shalong-tanwen
74f04336bb feat(mobile): Clip and change background for Asset selection (#3460) 2023-07-29 01:32:57 -05:00
Nahin Khan
54db2a48af Include information on GHCR login for docker installation (#3457)
Without authenticating to the GitHub Container Registry, users may struggle to install Immich server.
2023-07-28 22:30:50 -05:00
Nahin Khan
cde56d5a22 Clarify immich-admin command usage (#3456)
Some parts of the documentation state that administrative tasks like changing the admin password can be done via the `immich` function, but that is incorrect; instead, the `immich-admin` function should be used.
2023-07-28 22:30:05 -05:00
Alex The Bot
3f1cf44717 Version v1.71.0 2023-07-28 18:32:33 +00:00
martin
2d83ac4125 fix(web): focus & clear individual search term (#3452) 2023-07-28 13:03:23 -05:00
cfitzw
7147486b6a Sort album assets from newest to oldest (#3444) 2023-07-28 09:47:27 -05:00
Alex
89ddbac8bc chore: build report 2023-07-28 09:06:25 -05:00
Dmitry Brazhenko
e071b82e8a feat (web/server) 360 degrees Web panoramas [attempt 2] (#3412)
* commit 1 (isPanorama: boolean)

* working solution for projectiontypeenum

* fix

* format fix

* fix

* fix

* fix

* fix

* enum projectiontype

* working solution with exif

* fix

* reverted >

* fix format

* reverted auto-magic api.ts prettification

* fix

* reverted api.ts autogenerated

* api ts regenerated

* Update web/src/lib/components/assets/thumbnail/thumbnail.svelte

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* Update web/src/lib/components/asset-viewer/asset-viewer.svelte

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* exifProjectionType

* Update server/src/microservices/processors/metadata-extraction.processor.ts

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>

* projectionType?: string = ProjectionType.NONE;

* not null

* projectionType!: ProjectionType;

* opeapi generator fix

* fixes

* fix

* fix

* generate api

* asset.exifInifo?.projectionType

* Update server/src/domain/asset/response-dto/exif-response.dto.ts

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

* Update server/src/microservices/processors/metadata-extraction.processor.ts

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

* enum -> varchar;projectiontypeenum->projectiontype

* asset-viewer fixed prettiffier

* @Column({}) single line

* enum | string

* make api

* enum | string

* enum | str fix

* fix

* chore: use string instead of enum

* chore: open api

* fix: checks

---------

Co-authored-by: Sergey Kondrikov <sergey.kondrikov@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-07-27 23:29:09 -05:00
martin
13b2b2fc4e fix(web): clickable items in the searchbar (#3441) 2023-07-27 22:06:42 -05:00
Dhrumil Shah
fe9ef1a3ea feat(mobile) - Add better offline support (#3279)
* WIP: Adding init support for offline-loading

* WIP: found bug and fixed with offline browing adv setting

* WIP: big some bugs with first login

* WIP: static analysis fixes

* PR: Removed setting for offline browing

* PR: static analysis - remove imports

* PR: Refactored user login state

* PR: changed logger log level as it happens a lot

* PR: change log var to _log

* PR: addressing comments

* WIP: bug fixes

* WIP: static analysis on the logger variable
2023-07-27 22:05:27 -05:00
martin
afb0d0f54d feat(web): suggest to merge people faces when renaming a person name (#3399)
* feat: propose to merge faced based on the name

* responsive

* drop down menu

* add border

* improvements

* improvements

* improvements

* add comments

* responsive

* responsive

* feat: use FullScreenModal

* responsive

* pr feeback

* pr feeback

* pr feeback

* responsive

* pr feeback

* pr feeback

* styling

* fix test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-28 03:04:20 +00:00
martin
26085ff82b fix: clicking outside the shortcuts pannel (#3433)
* use FullScreenModal for ShowShortcuts

* pr feeback
2023-07-27 14:42:27 -05:00
Alex The Bot
2872886e77 Version v1.70.0 2023-07-27 03:40:16 +00:00
martin
a21112e4ab fix: people in shared assets (#3431)
* fix: people in shared assets

* use empty array
2023-07-26 21:14:50 -05:00
Jason Rasmussen
f3edf43158 chore: log listen address (#3428) 2023-07-26 18:29:35 +00:00
martin
1c5926553a fix: dialog overflow when creating a user (#3422) 2023-07-25 09:29:40 -05:00
faupau
05fa3092bf fix(web): fixes previous pull request: set asset as profile image (#3415)
* set photoviewer 100% width, fixes transparent ede

* remove unnecessary class

* format fix

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-25 05:17:59 +00:00
martyfuhry
7d3ec8af37 fix(mobile): Memory lane now updates to the correct day if the app is resumed the next day (#3414)
* Adds todayProvider to memory lane

* Revert "Adds todayProvider to memory lane"

This reverts commit 67ae58b513.

* Invalidate memory provider on app resume
2023-07-24 11:39:10 -05:00
Mark Monteiro
8db008ef0b Remove unnecessary PG_DATA environement variable from docker-compose.yml (#3394)
* Remove unnecessary PG_DATA environement variable from docker-compose.yml

There is no need to set the PostgreSQL data directory to the default location, it just adds an additional unnecessary line to the docker-compose file.

In addition, the PG_DATA isn't even the correct environment variable name (it should be PGDATA, see: https://hub.docker.com/_/postgres/), so this environment variable was never doing anything to begin with.

* Update docker-compose.dev.yml

* Update docker-compose.prod.yml

* Update docker-compose.test.yml
2023-07-23 21:11:27 -05:00
Alex
e493e05e99 fix(server): better facial recognition order (#3386) 2023-07-23 21:10:56 -05:00
martin
b83e535010 feat(web): show available shortcuts (#3342)
* feat(web): show available shortcuts

* pr feeback

* feat: new shortcut for deselect

* fix: remove new shortcut

* responsive
2023-07-23 21:09:06 -05:00
Daniele Ricci
111372edc1 fix(cli): fix wording in usage guide (#3378) 2023-07-23 21:06:27 -05:00
Mark Monteiro
625a899f64 Update environment-variables.md (#3402)
Add documentation for the environment variables that enable Docker secrets support

Support for these variables was implemented in #1254 and #3282
2023-07-23 17:53:52 -05:00
Alex
aaf0496f74 chore(server): Update Immich CLI version (#3403) 2023-07-23 17:53:20 -05:00
Alex
4977926c88 post mobile release 2023-07-23 13:51:48 -05:00
Alex The Bot
f41e1159d1 Version v1.69.0 2023-07-23 17:40:02 +00:00
Brian Di Palma
670107373b Update backup-and-restore.md (#3389)
* Update backup-and-restore.md

* Update backup-and-restore.md

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-07-23 12:01:36 -05:00
Brian Di Palma
e660f05c31 Update _storage-template.md (#3388)
* Update _storage-template.md

* Update _storage-template.md

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-07-23 11:53:58 -05:00
Dhrumil Shah
baf1ea313e feat(mobile): render login fields/buttons based on server configuration (#3339)
* WIP: Show login fields/buttons based on server configuration

* PR: change login disabled message to use translation

* added localization string)

* text

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-07-22 23:04:17 -05:00
martin
ed64c91da6 fix: hide faces (#3352)
* fix: hide faces

* remove unused variable

* fix: work even if one fails

* better style for hidden people

* add hide face in the menu dropdown

* add buttons to toggle visibility for all faces

* add server test

* close modal with escape key

* fix: explore page

* improve show & hide faces modal

* keep name on people card

* simplify layout

* sticky app bar in show-hide page

* fix format

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-22 22:00:43 -05:00
Alex Phillips
c40aa4399b fix(cli): read-only and sidecar support for import (#3372)
* added flag to support toggling read-only mode (read-only true by default), added sidecarPath to import request payload

* removed default on --no-read-only to prevent confusion since true is the default
2023-07-22 21:11:00 -05:00
Mert
8f08100a30 moved deps (#3379) 2023-07-22 15:04:52 -05:00
shalong-tanwen
337cd33042 fix(mobile): Decrease ScrollBar Fade timeout to a second (#3370) 2023-07-22 15:00:22 -05:00
martyfuhry
1e8fc7266c Fixes hide controls when zoomed and shows them when not zoomed (#3366) 2023-07-22 14:56:49 -05:00
martyfuhry
7f35583c2c feat(mobile): Precaches next image in memories (#3365)
* Precaches images in memories

* Fixes jumps and precaches images

* refactors to move precacheAsset over to ImmichImage to keep logic in same place

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-07-22 14:51:25 -05:00
Alex
ace755f264 fix(server): merged faces cannot be recognized in new photos (#3381)
* fix(server): merged faces cannot be recognized in new photos

* fix: delete stale documents
2023-07-22 14:42:12 -05:00
martyfuhry
7b25c9d0a7 fix(mobile): Uses gray box placeholder for loading images by default and fixes odd spinner (#3364)
* image loading

* Use gray box placeholder by default and fix odd spinner with normal spinner

* Progress indicator is separate

* Fixes loading for cached network image too

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-07-22 10:03:31 -05:00
shalong-tanwen
c0bee2a6b7 fix(mobile): AssetCount reset and Elliptical progress in Memories (#3355)
* fix: Constraint CircularProgressIndicator in Memories

* fix(mobile): Asset count reset when scroll cancelled midway in Memories
2023-07-21 23:56:49 -05:00
oddlama
b48d5cab22 fix(cli): move runtime dependencies to correct section (#3371)
and add missing dependency on typescript-eslint/parser
2023-07-21 23:01:20 -05:00
Simon Shields
4f59e6c7ab feat(server): Google Pixel motion photos (#3175)
* feat(server): Google Pixel motion photos

Add support for motion photos taken on Pixel phones. They have the exif
property 'MotionPhoto' set to 1, and an embedded mp4 file appended to
the JPEG file.

The implementation works like this:

- on metadata extraction, if a live photo is detected, examine the
  metadata to determine where in the file the embedded MP4 is.
- extract this MP4 and write it next to the JPEG.
- link it using the existing mechanism for live photos.

There is a "MotionPhotoPresentationTimestampUs" exif property, which we
don't do anything with - I imagine that it refers to the timepoint in
the video that the photo was taken at, but it probably warrants more
investigation.

* fix format

* fix test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-21 22:52:59 -05:00
Alex
82a5d54d2c fix(web): cannot upload file with uppercase extension (#3374)
* fix(web): cannot upload file with uppercase extension

* actual fix

* remove console log
2023-07-21 22:52:28 -05:00
Alex
5e6d830ecd fix(ml): startup issue in dev (#3373) 2023-07-21 21:10:08 -05:00
oddlama
f700f3427b feat(cli): add build script and unify tests (#3369) 2023-07-21 13:10:01 -05:00
dependabot[bot]
0c07c0ba4e chore(deps): bump stumpylog/image-cleaner-action from 0.1.0 to 0.2.0 (#3361)
Bumps [stumpylog/image-cleaner-action](https://github.com/stumpylog/image-cleaner-action) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/stumpylog/image-cleaner-action/releases)
- [Changelog](https://github.com/stumpylog/image-cleaner-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/image-cleaner-action/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: stumpylog/image-cleaner-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 11:32:12 -05:00
bt90
bc885f3644 fix(server): properly handle SIGTERM (#3350)
* use tini init

* Move python into CMD

* Use tini as entrypoint

* Toggle executable bit

* Avoid compose changes

* Adapt web entrypoint
2023-07-21 09:20:04 -05:00
Mert
6668964d92 transcode live photos on upload (#3354) 2023-07-20 20:21:38 -05:00
Alex The Bot
1835fbae49 Version v1.68.0 2023-07-20 03:06:12 +00:00
Alex
593489a14c fix(web): cannot use shift-select (#3343) 2023-07-19 21:15:22 -05:00
Alex
9f7bf36786 fix(web): cannot use semicolon on the search bar in asset grid page (#3334)
* fix(web): cannot use semicolon on the search bar

* fix(web): cannot use semicolon on the search bar

* remove console log

* fix: disable hotkey when search is enable

* format

* fix event listener removal
2023-07-19 11:03:23 -05:00
Thomas
f0302670d2 fix(server): add missing extensions and mime types (#3318)
Add extensions and mime types which were accidentally removed in #3197.

Fixes: #3300
2023-07-19 09:27:25 -05:00
Mert
4b8cc7b533 chore: docker compose for prod build (#3333)
* added docker compose for prod build

* updated makefile
2023-07-18 23:41:02 -05:00
Jason Rasmussen
6e953ff5eb fix(server): cancel error (#3332) 2023-07-18 23:40:20 -05:00
Alex
7316ad5a72 chore(web): sort tailwindcss class automatically (#3330) 2023-07-18 13:19:39 -05:00
martin
f28fc8fa5c feat(server,web): hide faces (#3262)
* feat: hide faces

* fix: types

* pr feedback

* fix: svelte checks

* feat: new server endpoint

* refactor: rename person count dto

* fix(server): linter

* fix: remove duplicate button

* docs: add comments

* pr feedback

* fix: get unhidden faces

* fix: do not use PersonCountResponseDto

* fix: transition

* pr feedback

* pr feedback

* fix: remove unused check

* add server tests

* rename persons to people

* feat: add exit button

* pr feedback

* add server tests

* pr feedback

* pr feedback

* fix: show & hide faces

* simplify

* fix: close button

* pr feeback

* pr feeback

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-18 13:09:43 -05:00
Alex
02b70e693c feat(web): add better face management UI action (#3328)
* add better face management menu

* context menu

* change name form

* change name

* navigate to merge face

* fix web
2023-07-18 12:36:20 -05:00
bo0tzz
b2e06477f8 chore: Enable logging on typesense container (#3326) 2023-07-18 11:19:16 -05:00
martin
632971a2ac fix: allow edit to empty name (#3322) 2023-07-17 21:20:28 -05:00
Thomas
8045fd3f14 fix(web): remove dependency on rxjs (#3301)
The dependency on rxjs has been removed in favour of iterators as it's clearer
and the nature of the workload is inherently non-reactive. The uncaught error
when the list of files is empty has also been implicitly fixed by this change.

Fixes: #3300
2023-07-17 11:22:29 -05:00
Alex
a2568f711f chore(mobile): remove things sections (#3309)
Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-07-17 11:20:05 -05:00
martin
f9032866e7 feat(web): new shortcuts (#3111)
* feat: shortcuts

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>

* fix: remove listener on component destroy

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>

* revert delete shortcut

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>

* feat: new notifications

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>

* fix: use handleError

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>

---------

Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>
2023-07-16 22:16:14 -05:00
Adam Cigánek
e287b18435 fix(mobile): fix forgetting backup albums (#3108) (#3244) 2023-07-17 03:08:58 +00:00
oddlama
c415ee82d1 chore: adjust loglevel of reverse geocoding intializer to LOG (#3303) 2023-07-16 21:57:20 -05:00
KailashGanesh
c8f1a15f21 fix(web): adjusted offset value to match header height (#3302) 2023-07-16 19:23:01 +00:00
Dhrumil Shah
9012cf6946 fix(mobile) - Allow sign out if server is down, or device is offline (#3275)
* WIP: Allow app sign out when server cannot be reached

* WIP: import logging lib

* WIP: move log out up
2023-07-15 20:52:41 -05:00
faupau
7595d01956 feat(web): set asset as profile picture (#3106)
* add profile-image-cropper component

* add dom-to-image library

* add store to update user profile picture when set

* dom-to-image

* remove console.logs, add svelte binding

* fix format, unused vars

* change caching of profile image

* set hash after profile image change

* remove unnecessary store

* remove unecesarry changes

* set types/dom-to-image as devDependency

* remove unecessary type declarations
use handleError

* remove error notification
which is already handled by handleError

* Revert "set types/dom-to-image as devDependency"

This reverts commit ca8b3ed1bb.

* add types do dev dependencies

* use on:close instead of on:close={()=>...}

* add newline

* sort imports

* bind photo-viewer imgElement directly, not working

* remove console.log, fix binding

* make imgElement optional

* fix element as optional prop

* fix type

* check for transparency

* small changes

* fix img.decode

* add bg, remove publicsharedkey

* fix omit publicSharedKey

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-15 20:31:33 -05:00
Sergey Kondrikov
ed3c239b7e fix(web): navigation buttons z-order (#3286)
* Fix navigation styling

* z-index
* refactor transition and hover

* Add NavigationButton and NavigationArea components

* Use group-hover to simplify hover styling

* fix check

* fix check

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-15 20:25:59 -05:00
Alex
c254a04aec feat(server): add endpoint to get supported media types on the server (#3284)
* feat(server): add endpoint to get supported media types on the server

* api generation

* remove xmp format

* change dto

* openapi

* dev
2023-07-15 20:24:46 -05:00
Alex
d5b96c0257 chore(web): Update to Svelte 4 (#3196)
* trying to update to svelte 4

* update dependencies

* remove global transition

* suppress wrning

* chore: install from github

* revert material icon change

* Supress a11y warning

* update

* remove coverage test on web

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-07-15 20:13:04 -05:00
Alex
436a2e9bf3 chore(mobile): share logo platform consistency (#3290) 2023-07-15 20:11:51 -05:00
Brian Di Palma
b34f4345e1 Update _storage-template.md (#3291) 2023-07-15 17:27:27 -05:00
Jason Rasmussen
f55d63fae8 feat(server): storage label claim (#3278)
* feat: storage label claim

* chore: open api
2023-07-15 14:50:29 -05:00
abhi-chakrab
ed594c1987 Update start.sh (#3282)
Adding ability to use docker secrets file for REDIS_PASSWORD
2023-07-15 10:30:52 -05:00
Skkay
ab85dd9fa8 chore(docs): Remove a duplicate word (#3285) 2023-07-15 10:06:34 -05:00
Jason Rasmussen
08c7054845 refactor(server): auth/oauth (#3242)
* refactor(server): auth/oauth

* fix: show server error message on login failure
2023-07-14 23:03:56 -05:00
Harry Tran
9ef41bf1c7 fix(web): update style of rows in user administration table (#3277) 2023-07-14 22:38:16 -05:00
Jason Rasmussen
1064128fde refactor(server): upload config (#3252) 2023-07-14 20:31:42 -05:00
Jason Rasmussen
382341f550 feat(web): show download size (#3270)
* feat(web): show download size

* chore: never over 100%

* chore: use percentage

* fix: unselect assets before download finishes
2023-07-14 20:25:13 -05:00
Alex The Bot
81e07fda08 Version v1.67.2 2023-07-14 14:01:20 +00:00
Jason Rasmussen
4c4435bc19 fix: serve absolute urls (#3265) 2023-07-14 08:58:14 -05:00
Jason Rasmussen
f952bc0b64 refactor(server): asset stats (#3253)
* refactor(server): asset stats

* chore: open api
2023-07-14 08:30:17 -05:00
Jason Rasmussen
05e1a6d949 chore: hide auto generated cli content (#3254) 2023-07-14 08:22:38 -05:00
xpwmaosldk
ea3d01ec62 chore(mobile): clean up (#3256) 2023-07-14 08:20:04 -05:00
Alex The Bot
2d4e2af629 Version v1.67.1 2023-07-14 02:45:02 +00:00
Jason Rasmussen
f18c2fd339 fix: exclude e2e format (#3250) 2023-07-13 21:41:16 -05:00
Alex The Bot
cd184cf366 Version v1.67.0 2023-07-14 00:39:54 +00:00
Alex
6387e38e27 fix(server): Asset controller does not load all endpoint (#3245) 2023-07-13 18:42:27 -05:00
Mert
2fb85f4a16 chore: rebase main (#3103)
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-07-13 16:02:49 -05:00
dependabot[bot]
34d1f74b77 chore(deps): bump docker/setup-buildx-action from 2.9.0 to 2.9.1 (#3236)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.9.0...v2.9.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-13 10:44:37 -05:00
Harry Tran
48c9cfb432 fix(web): remove processing key events in photo viewer window (#3238) 2023-07-13 10:43:11 -05:00
Fynn Petersen-Frey
f9739c9730 feat(mobile): stop asset grid rebuilds (#3226)
* feat(mobile): stop asset grid rebuilds

* undo unnecessary changes

---------

Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
2023-07-13 10:42:06 -05:00
Alex
863e983726 fix(web): download livephotos video part correctly (#3230) 2023-07-13 09:00:46 -05:00
Jason Rasmussen
b71d7e33bb refactor(server): calculate asset type server side (#3200)
* refactor(server): calculate asset type server-side

* chore: open api

* chore: remove comments

* fix: linting

* update

* Revert "update"

This reverts commit dc58702923.

* fix: upload LivePhotos

* chore: remove unused request fields for upload

* remove unused method

* mobile-fix: livePhoto filename

* fix: revert check for livephotos filename and extension

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-11 22:56:30 -05:00
Sergey Kondrikov
93462aafbc feat(web): improve range selection (#3193)
* Improve range selection

* Add comments

* Add PR feedback

* Remove focus outline from select asset button

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-11 21:12:19 -05:00
Jonathan Jogenfors
ea64fdd7b4 feat(server): Use Testcontainers for e2e tests (#3202)
* Add testcontainers to e2e

* Run e2e tests in github without docker

* Run lint on e2e

* Cleaner setup ordering

* Rename setup file
2023-07-11 16:54:44 -05:00
Alex
c86b2ae500 feat(web/server): merge faces (#3121)
* feat(server/web): Merge faces

* get parent id

* update

* query to get identical asset and change controller

* change delete asset signature

* delete identical assets

* gaming time

* delete merge person

* query

* query

* generate api

* pr feedback

* generate api

* naming

* remove unused method

* Update server/src/domain/person/person.service.ts

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

* Update server/src/domain/person/person.service.ts

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

* better method signature

* cleaning up

* fix bug

* added interfaces

* added tests

* merge main

* api

* build merge face interface

* api

* selector interface

* style

* more style

* clean up import

* styling

* styling

* better

* styling

* styling

* add merge face diablog

* finished

* refactor: merge person endpoint

* refactor: merge person component

* chore: open api

* fix: tests

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-07-11 16:52:41 -05:00
Mert
848ba685eb fix(ml): race condition when loading models (#3207)
* sync model loading, disabled model ttl by default

* disable revalidation if model unloading disabled

* moved lock
2023-07-11 12:01:21 -05:00
Jesaja Everling
9ad024c189 Fix typo (#3216) 2023-07-11 16:48:13 +00:00
Jason Rasmussen
0b15f6035b docs: add mobile links (#3214) 2023-07-11 10:10:28 -05:00
Jason Rasmussen
1e7b657156 docs: upgrade deps (#3215) 2023-07-11 10:09:19 -05:00
Jason Rasmussen
6180828ed2 refactor(server): mime types (#3197)
* refactor(server): mime type check

* chore: open api

* chore: remove duplicate test
2023-07-10 12:56:45 -05:00
Johan Stenehall
785f61ba70 Update read-only-gallery.md (#3191)
Prevent Immich from having write access to volumes that should only be read.
2023-07-09 20:19:48 -05:00
kasgel
50f26374e3 fix(server): enable transcoding of audioless videos (#3147)
* Fix: enable transcoding of audioless videos

* Fix: enable transcoding of audioless videos & typing

* Fix: enable transcoding of audioless videos & formatting

* fix: do not always transcode if there is no audio stream
2023-07-09 15:15:34 -05:00
Jason Rasmussen
398bd04ffd refactor(server): upload config (#3148) 2023-07-08 23:37:40 -05:00
Mert
8349a28ed8 refactor(server): modularize getFfmpegOptions (#3138)
* refactored `getFfmpegOptions`

refactor transcoding, make separate service

* fixed enum casing

* use `Logger` instead of `console.log`

* review suggestions

* use enum for `getHandler`

* fixed formatting

* Update server/src/domain/media/media.util.ts

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

* Update server/src/domain/media/media.util.ts

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

* More specific imports, renamed codec classes

* simplified code

* removed unused import

* added tests

* added base implementation for bitrate and threads

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-07-08 21:43:11 -05:00
faupau
27018e4ab6 feat(web): add emptyplaceholder when no assets (#3155)
* add emptyplace holder when no assets

* remove unecessary number type

* wording

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-08 21:32:34 -05:00
Alex
73e82303e7 [Localizely] Translations update (#3162) 2023-07-08 15:27:24 -05:00
Alex
64697235d6 feat(mobile): adding additional languages (#3161)
* update locale

* localizely

* Update info.plist

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-07-08 15:26:26 -05:00
Mert
a5cc408469 fix(server): thumbnail content type not being passed to stream handle (#3137)
* asset mimetype instead of application/octet-stream

* use thumbnail mimetype instead

* narrowed openapi spec

* thumbnail format validation

* JPEG fallback, `getThumbnailPath` returns format

* return content type in `getThumbnailPath`

* moved `format` validation to dto

* removed unused import

* moved fallback warning

* added `ApiOkResponse`
2023-07-08 15:07:56 -05:00
Fynn Petersen-Frey
d590dec159 Revert "feat(mobile): reduce UI rebuilds (#3129)" (#3159)
This reverts commit fe2330ebf6.
2023-07-08 15:03:54 -05:00
Alex
b262bcec03 [Localizely] Translations update (#3158) 2023-07-08 10:09:34 -05:00
Fynn Petersen-Frey
fe2330ebf6 feat(mobile): reduce UI rebuilds (#3129) 2023-07-08 08:46:22 -05:00
Mert
50c7b35291 clarified openapi use (#3149) 2023-07-07 20:49:18 -05:00
dependabot[bot]
927d6ab1c6 chore(deps): bump docker/setup-buildx-action from 2.8.0 to 2.9.0 (#3145)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.8.0...v2.9.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-07 08:57:30 -05:00
Alex
d064477a45 fix(web): Places and Things thumbnail cannot be clicked (#3133)
* fix(web): Places and Things thumbnail cannot be clicked

* fix openapi

* fix test
2023-07-06 17:34:15 -05:00
Jordi Altayó
6588bb3d79 Add catalan translation of readme (#3130)
* Create README_ca_ES.md

* Update README_ca_ES.md

* Update README.md

* Update README_ca_ES.md

* Update README_ca_ES.md

* Update README_ca_ES.md
2023-07-06 17:28:59 -05:00
Alex
812cb3d940 Revert "fix(server): use thumbnail content type instead of application/octet-stream (#3075)" (#3134)
* Revert "fix(server): use thumbnail content type instead of application/octet-stream (#3075)"

This reverts commit 3cc77d945b.

* generate api
2023-07-06 17:25:56 -05:00
Alex Tran
852ef3cd1b chore(server): update openapi 2023-07-06 16:56:02 -05:00
Mert
3cc77d945b fix(server): use thumbnail content type instead of application/octet-stream (#3075)
* asset mimetype instead of application/octet-stream

* use thumbnail mimetype instead

* narrowed openapi spec

* thumbnail format validation

* JPEG fallback, `getThumbnailPath` returns format

* return content type in `getThumbnailPath`

* moved `format` validation to dto

* removed unused import

* moved fallback warning
2023-07-06 10:27:21 -05:00
Jonathan Jogenfors
6f4449d5e9 feat(cli) Add new CLI (#3066)
* Add new cli

* Remove old readme

* Add documentation to readme file

* Add github workflow tests for cli

* Fix typo in docs

* Add usage info to readme

* Add package-lock.json

* Fix tsconfig

* Cleanup

* Fix lint

* Cleanup package.json

* Fix accidental server change

* Remove rootdir from cli

* Remove tsbuildinfo

* Add prettierignore

* Make CLI use internal openapi specs

* Add ignore and dry-run features

* Sort paths alphabetically

* Don't remove substring

* Remove shorthand for delete

* Remove unused import

* Remove chokidar

* Set correct openapi cli generator script

* Add progress bar

* Rename target to asset

* Add deletion progress bar

* Ignore require statement

* Use read streams instead of readfile

* Fix github feedback

* Fix upload requires

* More github comments

* Cleanup messages

* Cleaner pattern concats

* Github comments

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-06 09:37:47 -05:00
Sergey Kondrikov
37edef834e fix(web): previous/next asset navigation (#3107)
* fix(web): previous/next asset navigation

* Apply suggestions from code review

Co-authored-by: Thomas <9749173+uhthomas@users.noreply.github.com>

* Call setViewingAsset once

* Make code more readable

* Avoid recursive call

* Simplify return statement

* Set position of the bucket to Unknown

---------

Co-authored-by: Thomas <9749173+uhthomas@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-07-05 15:24:23 -05:00
Rohitt Vashishtha
814030be77 fix(server): Set dev restart policy to unless-stopped. (#3123)
Previously, if you'd shut down the `make-dev` command and restart
the docker daemon (say, on a system reboot), there would be 3 immich
containers already running.
2023-07-05 15:23:23 -05:00
Mert
71a2914f3e fix(server): transcodes failing due to storage migration happening simultaneously (#3071) 2023-07-05 00:36:16 -05:00
Alex The Bot
0d30ceb284 Version v1.66.1 2023-07-05 02:50:53 +00:00
Alex
4add6cb26e fix(web): Thumbnail not disappear after performing actions on the timeline (#3116)
* fix(web): Thumbnail not dissapear after action

* actual cause

* actual cause
2023-07-04 21:48:21 -05:00
Alex The Bot
8a3ab5be3e Version v1.66.0 2023-07-04 15:51:53 +00:00
Sergey Kondrikov
8e18acff85 feat(web): enhance date group title (#3094)
Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-07-03 12:04:46 +00:00
Thomas
8fd4edb206 feat(web): select a range of assets (#3086)
The shift key can be held to select a range of assets.

Fixes: #2862
2023-07-03 09:56:58 +00:00
Mert
2099b04057 fix(server): Premature stream close error when viewing videos in web (#3093)
* suppress 'ERR_STREAM_PREMATURE_CLOSE'

* refactor stream range logic
2023-07-02 21:37:12 -05:00
Thomas
1a0a3aa2c1 fix(web): use natural asset order for navigation (#3092) 2023-07-02 21:02:38 -05:00
Alex
7947f4db4c feat(web/server): Face thumbnail selection (#3081)
* add migration

* verify running migration populate new value

* implemented service

* generate api

* FE works

* FR Works

* fix test

* fix test fixture

* fix test

* fix test

* consolidate api

* fix test

* added test

* pr feedback

* refactor

* click ont humbnail to show feature selection as well
2023-07-02 17:46:20 -05:00
Alex
1df068bac9 chore(server): add limit to people return (#3069) 2023-07-02 15:28:53 -05:00
Dhrumil Shah
d9e084706f chore(docs): clarifications to --import flag on CLI docs (#2996)
* Clarifications to `--import` flag on CLI docs

* WIP: Fixed heading, added some more info

* PR: fixing format issues
2023-07-01 13:33:04 -05:00
Alex Tran
55e7893bad docs: type 2023-07-01 13:30:59 -05:00
Friso Smit
604b10778c Add more detail to reverse proxy docs (#2841)
* Add more detail to reverse proxy docs

Document fix for issue #2564

* Add port to the proxy_pass example
2023-07-01 13:30:19 -05:00
Jason Rasmussen
d69fa3ceae refactor(server): guards, decorators, and utils (#3060) 2023-07-01 13:27:34 -05:00
Jason Rasmussen
f55b3add80 chore(web): prettier (#2821)
Co-authored-by: Thomas Way <thomas@6f.io>
2023-06-30 23:50:47 -05:00
Jason Rasmussen
7c2f7d6c51 chore: remove refactored controllers from unit test coverage (#3063) 2023-06-30 23:47:28 -05:00
Jason Rasmussen
19cc94e594 refactor(server): use better algorithm for calculating the duplicate filename (#3061) 2023-06-30 23:44:55 -05:00
Jason Rasmussen
b93bbc9f5d refactor(server): storage template core (#3059) 2023-06-30 23:43:24 -05:00
Jason Rasmussen
2feac54382 refactro(server): job dto (#3057) 2023-06-30 23:41:12 -05:00
Jason Rasmussen
49f1f6cad7 refactor(server): person dto (#3058) 2023-06-30 20:52:40 -05:00
Jason Rasmussen
399312ead3 refactor(server): api key auth (#3054) 2023-06-30 20:49:30 -05:00
Mert
f9671dfbf7 fix(server): h264 videos failing to transcode in two-pass mode (#3053)
* set `-fps_mode` to passthrough

* updated tests
2023-06-30 20:48:40 -05:00
Mert
b1fcf02d13 fix(server): h264 and hevc not respecting max bitrate (#3052)
* added `-bufsize` flag

* updated test
2023-06-30 20:48:05 -05:00
Fynn Petersen-Frey
615893be38 fix(mobile): setting to always display remote assets (#3044) 2023-06-30 20:47:44 -05:00
Ethan Margaillan
5869648f19 chore(web): replace window.confirm by ConfirmDialogues and cleanup existing ones (#3039)
* chore(web): replace window.confirm by ConfirmDialogues and cleanup existing ones

* fix(web): linter and svelte-check issues

* fix(web): rephrase some confirm dialogs

* fix(web): run prettier

* fix(web): merge with last version and run prettier again

* fix(web): run prettier
2023-06-30 14:53:16 -05:00
martyfuhry
734f8e02b5 fix(mobile): Uses ImageFiltered for performance (#3051)
* Uses ImageFiltered for performance

* values

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-30 13:49:17 -05:00
Mert
e477f99c7d fix(server): fix more vector search results being returned than intended (#3042) 2023-06-30 11:33:54 -05:00
Jason Rasmussen
455a36b0fc fix(server): read file permission checks (#3046) 2023-06-30 11:25:08 -05:00
Jason Rasmussen
ad343b7b32 refactor(server): download assets (#3032)
* refactor: download assets

* chore: open api

* chore: finish tests, make size configurable

* chore: defualt to 4GiB

* chore: open api

* fix: optional archive size

* fix: bugs

* chore: cleanup
2023-06-30 11:24:28 -05:00
Alex The Bot
df9c05bef3 Version v1.65.0 2023-06-30 03:01:48 +00:00
Alex Tran
6c8c16c85f chore: update release note notes 2023-06-29 21:48:57 -05:00
Alex
b05f3fd266 fix(mobile): avatar without last name (#3038) 2023-06-29 17:05:12 -05:00
Alex
ca98d73d86 chore(mobile): update flutter to 3.10.5 (#3036) 2023-06-29 16:28:18 -05:00
Fynn Petersen-Frey
b7ae3be394 fix(mobile): rework album detail page header (#3035) 2023-06-29 16:11:56 -05:00
Alex Tran
621fa5ba54 update readme 2023-06-29 15:23:55 -05:00
Alex Tran
ca1b9bf7b3 fix(doc): format 2023-06-29 14:49:23 -05:00
Dhrumil Shah
6fa685d9d8 chore: add CLI tool to the server image (#2999)
* WIP: Added immich cli tool to `immich-server` image

* WIP: Added doc entry to show it is preinstalled

* WIP: Moved immich upload cli to `immich` and default to `immich-admin`

* WIP: undid previous commit

* WIP: Updated server docs with new `immich-admin` command
2023-06-29 14:48:16 -05:00
Fynn Petersen-Frey
ff26d3666e fix(mobile): set scrolling state only if changed (#3034)
* fix(mobile): set scrolling state only if changed

* fix: generate api

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-29 14:35:29 -05:00
faupau
e3557fd80e Fix(web): drag n drop shared link (#3030)
* add event to trigger uploadhandler

* add dragndrop store
to handle upload in album-viewer and individuel-shared-viewer
(only on shares)

* fix handleUploadAssets no parameter

* fix format
2023-06-29 10:26:25 -05:00
faupau
c065705608 fix(web): Share link multi-select download icon showing when not available #3006 (#3027)
* only show download button if allowDownload
add SelectAll to individual share

* fix allow download if not share
2023-06-29 10:11:37 -05:00
dependabot[bot]
3948247055 chore(deps): bump docker/setup-buildx-action from 2.7.0 to 2.8.0 (#3028)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-29 08:11:17 -05:00
Sergey Kondrikov
dca48d7722 fix(web): aspect ratio for videos (#3023) 2023-06-29 08:11:00 -05:00
Alex
8e6c90e294 chore(mobile): minor UI tweak (#3021)
* chore(mobile): minor UI tweak

* fix test

* refactor
2023-06-28 22:33:57 -05:00
Thomas
e5908f2508 fix(server): use private cache (#3017)
The omission of additional cache-control directives implied the resource could
be stored in shared/public caches, which is not desirable.

In addition, the no-transform directive will ensure content is not
unintentionally mangled.

Fixes: #3014
2023-06-28 21:26:16 -05:00
martin
fbd98ec0f9 feat(web): persist info panel (#3013)
Signed-off-by: martabal <74269598+martabal@users.noreply.github.com>
2023-06-28 21:14:16 -05:00
Fynn Petersen-Frey
1ab05e8de0 fix(mobile): fix endless rendering of asset grid when scrolling (#3010) 2023-06-28 21:13:18 -05:00
Sergey Kondrikov
86562f256f fix(web): aspect ratio for photos with Rotate 270 CW orientation (#3003)
* fix(web): aspect ratio for photos with Rotate 270 CW orientation

* Remove checks assuming we can have only numeric values

* Remove the -90 value check for the orientation

* Add comment to numeric values of the orientation tag
2023-06-28 13:04:32 -05:00
Jason Rasmussen
add5219d34 fix: live photos not playing in shared links/albums (#3008) 2023-06-28 12:58:38 -05:00
Keszei Balázs
6ae5d11ec0 chore(server): Image description disappears after toggle favorite (#3009)
* Fixed asset rewrite description on toggle favorite

* Fixed description removing error

* Rewrite description condition for asset update

---------

Co-authored-by: Balazs Keszei <balazs.keszei@clbr.hu>
2023-06-28 12:54:48 -05:00
Rohitt Vashishtha
b4e641548c fix(web): Add m: to search query upon loading results. (#2954)
Previously, we'd drop the m: from non-clip searches entirely. This
behavior incorrectly represents the page's status (results from
non-clip search but query implies a clip search). Also, any follow-up
searches change to clip searches, which feels like a jarring UX if you
have to add m: every time in a 'search-session'.
2023-06-28 17:48:53 +00:00
Mert
017214fd56 fix(server): empty tag responses should be considered valid (#2993)
* accept empty tag array

* renamed test
2023-06-28 09:40:30 -05:00
Alex Phillips
22a73b67d3 chore(server): check file extension for XMP instead of mimetype (#2990)
* just check file extension for XMP instead of mimetype

* use path to get extension instead of regex

* single quotes

* remove unused import

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-28 14:40:21 +00:00
Thomas
792ecc6cac fix(server): add missing avi mime types and add tests (#3001)
See https://github.com/immich-app/immich/pull/2952#pullrequestreview-1497194041

Fixes: #2975
2023-06-28 09:21:42 -05:00
Jason Rasmussen
e98398cab8 refactor(server): access permissions (#2910)
* refactor: access repo interface

* feat: access core

* fix: allow shared links to add to a shared link

* chore: comment out unused code

* fix: pr feedback

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-06-28 08:56:24 -05:00
Mert
df1e8679d9 chore(ml): added testing and github workflow (#2969)
* added testing

* github action for python, made mypy happy

* formatted with black

* minor fixes and styling

* test model cache

* cache test dependencies

* narrowed model cache tests

* moved endpoint tests to their own class

* cleaned up fixtures

* formatting

* removed unused dep
2023-06-27 18:21:33 -05:00
Alex Tran
5e3bdc76b2 chore(mobile): auto dispose future provider 2023-06-27 16:02:54 -05:00
Mert
47982641b2 fix(ml): clear model cache on load error (#2951)
* clear model cache on load error

* updated caught exceptions
2023-06-27 16:01:24 -05:00
Alex
39a885a37c feat(mobile): memories (#2988)
* Add page view

* Nice page view

* refactor file structure

* Added card

* invalidating data

* transition

* styling

* correct styleing

* refactor

* click to navigate

* styling

* TODO

* clean up

* clean up

* pr feedback

* pr feedback

* better loading indicator
2023-06-27 16:00:20 -05:00
Alex Tran
0e8d235148 fix(mobile): format 2023-06-27 12:28:15 -05:00
Alex Elkins
053a5235be chore(mobile): Capitalize Places cities in app (#2985) 2023-06-27 17:26:23 +00:00
Fynn Petersen-Frey
de42ebf3d8 feat(Android): find & delete corrupt asset backups (#2963)
* feat(mobile): find & delete corrupt asset backups

* show backup fix only for advanced troubleshooting
2023-06-27 12:25:00 -05:00
Mert
4d3ce0a65e fixed setting different clip, removed unused stubs (#2987) 2023-06-27 12:21:50 -05:00
Alex
b3e97a1a0c chore(web): Only show Copy button in HTTPS context (#2983) 2023-06-27 08:49:20 -05:00
Sergey Kondrikov
f5d9826b12 Fix download asset loading indicator position (#2974) 2023-06-27 08:48:20 -05:00
Alex
61e5e65173 feat(server): Add camera make and model to search criteria (#2982) 2023-06-27 06:53:07 -05:00
1146 changed files with 99016 additions and 53562 deletions

12
.gitattributes vendored
View File

@@ -2,12 +2,16 @@ mobile/openapi/**/*.md -diff -merge
mobile/openapi/**/*.md linguist-generated=true
mobile/openapi/**/*.dart -diff -merge
mobile/openapi/**/*.dart linguist-generated=true
mobile/openapi/.openapi-generator/FILES -diff -merge
mobile/openapi/.openapi-generator/FILES linguist-generated=true
cli/src/api/open-api/**/*.md -diff -merge
cli/src/api/open-api/**/*.md linguist-generated=true
cli/src/api/open-api/**/*.ts -diff -merge
cli/src/api/open-api/**/*.ts linguist-generated=true
web/src/api/open-api/**/*.md -diff -merge
web/src/api/open-api/**/*.md linguist-generated=true
web/src/api/open-api/**/*.ts -diff -merge
web/src/api/open-api/**/*.ts linguist-generated=true
mobile/openapi/.openapi-generator/FILES -diff -merge
mobile/openapi/.openapi-generator/FILES linguist-generated=true

View File

@@ -45,7 +45,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.0"
flutter-version: "3.13.0"
cache: true
- name: Create the Keystore

View File

@@ -38,7 +38,7 @@ jobs:
-
name: Clean temporary images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.1.0
uses: stumpylog/image-cleaner-action/ephemeral@v0.2.0
with:
token: "${{ env.TOKEN }}"
owner: "immich-app"
@@ -70,7 +70,7 @@ jobs:
-
name: Clean untagged images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.1.0
uses: stumpylog/image-cleaner-action/untagged@v0.2.0
with:
token: "${{ env.TOKEN }}"
owner: "immich-app"

View File

@@ -24,18 +24,15 @@ jobs:
fail-fast: false
matrix:
include:
- context: "server"
image: "immich-server"
platforms: "linux/arm/v7,linux/amd64,linux/arm64"
- context: "web"
image: "immich-web"
platforms: "linux/arm/v7,linux/amd64,linux/arm64"
platforms: "linux/amd64,linux/arm64"
- context: "machine-learning"
image: "immich-machine-learning"
platforms: "linux/amd64,linux/arm64"
- context: "nginx"
image: "immich-proxy"
platforms: "linux/arm/v7,linux/amd64,linux/arm64"
platforms: "linux/amd64,linux/arm64"
steps:
- name: Checkout
@@ -45,7 +42,91 @@ jobs:
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.7.0
uses: docker/setup-buildx-action@v2.9.1
# Workaround to fix error:
# failed to push: failed to copy: io: read/write on closed pipe
# See https://github.com/docker/build-push-action/issues/761
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to Docker Hub
# Only push to Docker Hub when making a release
if: ${{ github.event_name == 'release' }}
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
# Skip when PR from a fork
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@v4
with:
flavor: |
# Disable latest tag
latest=false
images: |
name=ghcr.io/${{ github.repository_owner }}/${{matrix.image}}
name=altran1502/${{matrix.image}},enable=${{ github.event_name == 'release' }}
tags: |
# Tag with branch name
type=ref,event=branch
# Tag with pr-number
type=ref,event=pr
# Tag with git tag on release
type=ref,event=tag
type=raw,value=release,enable=${{ github.event_name == 'release' }}
- name: Determine build cache output
id: cache-target
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
# Essentially just ignore the cache output (PR can't write to registry cache)
echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
else
echo "cache-to=type=registry,mode=max,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{ matrix.image }}" >> $GITHUB_OUTPUT
fi
- name: Build and push image
uses: docker/build-push-action@v4.1.1
with:
context: ${{ matrix.context }}
platforms: ${{ matrix.platforms }}
# Skip pushing when PR from a fork
push: ${{ !github.event.pull_request.head.repo.fork }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{matrix.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
build_and_push_server_arm_64:
runs-on: self-hosted
strategy:
# Prevent a failure in one image from stopping the other builds
fail-fast: false
matrix:
include:
- context: "server"
image: "immich-server"
platforms: "linux/arm64,linux/amd64"
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.1
# Workaround to fix error:
# failed to push: failed to copy: io: read/write on closed pipe
# See https://github.com/docker/build-push-action/issues/761

View File

@@ -83,4 +83,5 @@ jobs:
files: |
docker/docker-compose.yml
docker/example.env
docker/hwaccel.yml
*.apk

View File

@@ -23,7 +23,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.0"
flutter-version: "3.13.0"
- name: Install dependencies
run: dart pub get

View File

@@ -13,13 +13,20 @@ jobs:
e2e-tests:
name: Run end-to-end test suites
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./server
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run Immich Server E2E Test
run: docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test up --abort-on-container-exit --exit-code-from immich-server-test
- name: Run npm install
run: npm ci
- name: Run e2e tests
run: npm run test:e2e
if: ${{ !cancelled() }}
doc-tests:
name: Run documentation checks
@@ -73,6 +80,32 @@ jobs:
run: npm run test:cov
if: ${{ !cancelled() }}
cli-unit-tests:
name: Run cli test suites
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./cli
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run npm install
run: npm ci
- name: Run linter
run: npm run lint
if: ${{ !cancelled() }}
- name: Run formatter
run: npm run format
if: ${{ !cancelled() }}
- name: Run unit tests & coverage
run: npm run test:cov
if: ${{ !cancelled() }}
web-unit-tests:
name: Run web unit test suites and checks
runs-on: ubuntu-latest
@@ -103,9 +136,9 @@ jobs:
run: npm run check:typescript
if: ${{ !cancelled() }}
- name: Run unit tests & coverage
run: npm run test:cov
if: ${{ !cancelled() }}
# - name: Run unit tests & coverage
# run: npm run test:cov
# if: ${{ !cancelled() }}
mobile-unit-tests:
name: Run mobile unit tests
@@ -116,11 +149,42 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.0"
flutter-version: "3.13.0"
- name: Run tests
working-directory: ./mobile
run: flutter test -j 1
ml-unit-tests:
name: Run ML unit tests and checks
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./machine-learning
steps:
- uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v4
with:
python-version: 3.11
cache: "poetry"
- name: Install dependencies
run: |
poetry install --with dev
poetry run pip install --no-deps -r requirements.txt
- name: Lint with ruff
run: |
poetry run ruff check --format=github app
- name: Check black formatting
run: |
poetry run black --check app
- name: Run mypy type checking
run: |
poetry run mypy --install-types --non-interactive app/
- name: Run tests and coverage
run: |
poetry run pytest --cov app
generated-api-up-to-date:
name: Check generated files are up-to-date
runs-on: ubuntu-latest

View File

@@ -23,10 +23,10 @@ test-e2e:
docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
prod:
docker-compose -f ./docker/docker-compose.yml up --build -V --remove-orphans
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
prod-scale:
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
docker-compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
api:
cd ./server && npm run api:generate

View File

@@ -20,13 +20,16 @@
<p align="center">
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
</p>
## Disclaimer
- ⚠️ The project is under **very active** development.
- ⚠️ Expect bugs and breaking changes.
- ⚠️ **Do not use the app as the only way to store your photos and videos!**
- ⚠️ **Do not use the app as the only way to store your photos and videos.**
- ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
## Content
@@ -70,7 +73,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Multi-user support | Yes | Yes |
| Album and Shared albums | Yes | Yes |
| Scrubbable/draggable scrollbar | Yes | Yes |
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes |
| Support raw formats | Yes | Yes |
| Metadata view (EXIF, map) | Yes | Yes |
| Search by metadata, objects, faces, and CLIP | Yes | Yes |
| Administrative functions (user management) | No | Yes |
@@ -84,8 +87,10 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Archive and Favorites | Yes | Yes |
| Global Map | No | Yes |
| Partner Sharing | Yes | Yes |
| Facial recognition and clustering | No | Yes |
| Facial recognition and clustering | Yes | Yes |
| Memories (x years ago) | Yes | Yes |
| Offline support | Yes | No |
| Read-only gallery | Yes | Yes |
# Support the project

108
README_ca_ES.md Normal file
View File

@@ -0,0 +1,108 @@
<p align="center">
<br/>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=Llicència&logoColor=000000&labelColor=ececec" alt="Llicència: MIT"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
</a>
<br/>
<br/>
</p>
<p align="center">
<img src="design/immich-logo.svg" width="150" title="Iniciar sessió amb URL personalitzada">
</p>
<h3 align="center">Immich - Solució de còpia de seguretat d'alta rendiment per a fotos i vídeos auto-allotjada</h3>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Captura de pantalla principal">
</a>
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Español</a>
</p>
## Avís legal
- ⚠️ El projecte està en desenvolupament **molt actiu**.
- ⚠️ Espereu errors i canvis que poden trencar coses.
- ⚠️ **No utilitzeu l'aplicació com a única manera de guardar les vostres fotos i vídeos!**
## Contingut
- [Documentació oficial](https://immich.app/docs)
- [Mapa de ruta](https://github.com/orgs/immich-app/projects/1)
- [Demo](#demo)
- [Funcionalitats](#funcionalitats)
- [Introducció](https://immich.app/docs/overview/introduction)
- [Instal·lació](https://immich.app/docs/install/requirements)
- [Directrius de contribució](https://immich.app/docs/overview/support-the-project)
- [Donar suport al projecte](#suportar-el-projecte)
## Documentació
Podeu trobar la documentació principal, incloent les guies d'instal·lació, a https://immich.app/.
## Demo
Podeu accedir a la demostració web a https://demo.immich.app
Per a l'aplicació mòbil, podeu utilitzar `https://demo.immich.app/api` com a "URL de punt final del servidor".
```bash title="Credencials de la demo"
Les credencials
email: demo@immich.app
contrasenya: demo
```
```
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
# Funcionalitats
| Característiques | Mòbil | Web |
| -------------------------------------------- | ------ | --- |
| Pujar i veure vídeos i fotos | Sí | Sí |
| Còpia de seguretat automàtica en obrir l'aplicació | Sí | N/A |
| Selecció d'àlbums per a la còpia de seguretat | Sí | N/A |
| Descarregar fotos i vídeos a l'aparell local | Sí | Sí |
| Suport per a múltiples usuaris | Sí | Sí |
| Àlbums i àlbums compartits | Sí | Sí |
| Barra de desplaçament amb funció de rasclet/arrossegament | Sí | Sí |
| Suport per a formats raw | Sí | Sí |
| Visualització de metadades (EXIF, mapa) | Sí | Sí |
| Cerca per metadades, objectes, cares i CLIP | Sí | Sí |
| Funcions administratives (gestió d'usuaris) | No | Sí |
| Còpia de seguretat en segon pla | Sí | N/A |
| Desplaçament virtual | Sí | Sí |
| Suport per a OAuth | Sí | Sí |
| Claus d'API | N/A | Sí |
| Còpia de seguretat i reproducció de LivePhoto | iOS | Sí |
| Estructura d'emmagatzematge definida per l'usuari | Sí | Sí |
| Compartició pública | No | Sí |
| Arxiu i preferits | Sí | Sí |
| Mapa global | No | Sí |
| Compartició amb associats | Sí | Sí |
| Reconeixement facial i agrupament | Sí | Sí |
| Records (fa x anys) | Sí | Sí |
| Suport fora de línia | Sí | No |
| Galeria de només lectura | Sí | Sí |
# Donar suport al projecte
M'he compromès amb aquest projecte i no em detindré. Continuaré actualitzant la documentació, afegint noves funcionalitats i solucionant errors. Però no ho puc fer sol. Per això, necessito la vostra ajuda per donar-me motivació addicional per seguir endavant.
Com van dir els nostres amfitrions a l'episodi [selfhosted.show - 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418), això és una tasca enorme del que l'equip i jo estem fent. I m'encantaria poder dedicar-m'hi a temps complet, per la qual cosa us demano la vostra ajuda per fer-ho possible.
Si creieu que aquesta és una causa justa i l'aplicació és alguna cosa que us veieu utilitzant durant molt de temps, considereu donar suport al projecte amb alguna de les opcions següents.
## Donació
- [Donació mensual](https://github.com/sponsors/alextran1502) a través de GitHub Sponsors
- [Donació única](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) a través de GitHub Sponsors
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX

108
README_es_ES.md Normal file
View File

@@ -0,0 +1,108 @@
<p align="center">
<br/>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="Licencia: MIT"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
</a>
<br/>
<br/>
</p>
<p align="center">
<img src="design/immich-logo.svg" width="150" title="Iniciar sesión con URL personalizada">
</p>
<h3 align="center">Immich: Una solución Self-Hosted de copia de seguridad de fotos y videos de alto rendimiento</h3>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Captura de pantalla principal">
</a>
<br/>
<p align="center">
<a href="README.md">English</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Català</a>
</p>
## Descargo de responsabilidad
- ⚠️ El proyecto está en **desarrollo muy activo**.
- ⚠️ Es probable que haya errores y cambios disruptivos.
- ⚠️ **¡No utilices la aplicación como única forma de almacenar tus fotos y videos!**
## Contenido
- [Documentación oficial](https://immich.app/docs)
- [Hoja de ruta](https://github.com/orgs/immich-app/projects/1)
- [Demostración](#demo)
- [Funciones](#features)
- [Introducción](https://immich.app/docs/overview/introduction)
- [Instalación](https://immich.app/docs/install/requirements)
- [Directrices para contribuir](https://immich.app/docs/overview/support-the-project)
- [Apoya el proyecto](#support-the-project)
## Documentación
Puedes encontrar la documentación principal, incluidas las guías de instalación, en <https://immich.app/>.
## Demostración
Puedes acceder a la demostración web en <https://demo.immich.app>
Para la aplicación móvil, puedes usar `https://demo.immich.app/api` como `URL de la terminal del servidor`.
```bash title="Credenciales de la demostración"
Las credenciales son
correo electrónico: demo@immich.app
contraseña: demo
```
```bash
Especificaciones: VM de nivel gratuito de Oracle - Ámsterdam - CPU ARM64 de cuatro núcleos a 2.4 GHz, 24 GB de RAM
```
## Funcionalidades
| Funcionalidades | Móvil | Web |
| ----------------------------------------------------- | ------ | --- |
| Cargar y ver videos y fotos | Sí | Sí |
| Copia de seguridad automática al abrir la aplicación | Sí | N/D |
| Álbum(es) selectivo(s) para copia de seguridad | Sí | N/D |
| Descargar fotos y videos al dispositivo local | Sí | Sí |
| Soporte multiusuario | Sí | Sí |
| Álbum y álbumes compartidos | Sí | Sí |
| Barra de desplazamiento con función de búsqueda | Sí | Sí |
| Soporte para formatos RAW | Sí | Sí |
| Visualización de metadatos (EXIF, map) | Sí | Sí |
| Búsqueda por metadatos, objetos, rostros y CLIP | Sí | Sí |
| Funciones administrativas (gestión de usuarios) | No | Sí |
| Copia de seguridad en segundo plano | Sí | N/D |
| Desplazamiento virtual | Sí | Sí |
| Soporte de OAuth | Sí | Sí |
| Claves de API | N/D | Sí |
| Copia de seguridad y reproducción de LivePhoto | iOS | Sí |
| Estructura de almacenamiento definida por el usuario | Sí | Sí |
| Compartir públicamente | No | Sí |
| Archivar y marcar como favorito | Sí | Sí |
| Mapa global | No | Sí |
| Compartir con colaboradores | Sí | Sí |
| Reconocimiento facial y agrupación | Sí | Sí |
| Recuerdos (hace x años) | Sí | Sí |
| Soporte sin conexión | Sí | No |
| Galería de solo lectura | Sí | Sí |
## Apoya el proyecto
Me he comprometido con este proyecto, y no me detendré. Continuaré actualizando la documentación, agregando nuevas funcionalidades y corrigiendo errores. Pero no puedo hacerlo solo. Por eso, necesito tu ayuda para darme una motivación adicional para seguir adelante.
Como dijeron nuestros anfitriones en [selfhosted.show - En el episodio 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418), esto es una gran tarea de lo que el equipo y yo estamos haciendo. Y me encantaría poder dedicarme a esto a tiempo completo algún día, así que te pido tu ayuda para que eso sea posible.
Si consideras que esta es una causa justa y la aplicación es algo que te gustaría usar durante mucho tiempo, por favor, considera apoyar el proyecto con las siguientes opciones.
## Donación
- [Donación mensual](https://github.com/sponsors/alextran1502) a través de GitHub Sponsors
- [Donación única](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) a través de GitHub Sponsors
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX

View File

@@ -20,6 +20,8 @@
<p align="center">
<a href="README.md">English</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
</p>
## Feragatname

View File

@@ -13,7 +13,7 @@
</p>
<h3 align="center">Immich - 高性能的自托管照片和视频备份方案</h3>
<p align="center">
请注意: 此README不是由Immich团队维护, 这意味着它在某一时间点不会被更新,因为我们是依靠贡献者来更新的。感谢理解。
请注意: 此 README 不是由 Immich 团队维护, 而是依靠贡献者来更新的,这意味着它可能并不会被及时更新。感谢理解。
</p>
<br/>
<a href="https://immich.app">
@@ -24,34 +24,38 @@
<p align="center">
<a href="README.md">English</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
</p>
## 免责声明
- ⚠️ 本项目正在 **非常活跃** 开发中。
- ⚠️ 可能存在bug或者重大变更。
- ⚠️ **不要把本软件作为存储照片或视频的唯一方式!**
- ⚠️ 本项目正在 **非常活跃** 开发中。
- ⚠️ 可能存在 bug 或者随时有重大变更。
- ⚠️ **不要把本软件作为存储照片或视频的唯一方式**
- ⚠️ 为了您宝贵的照片与视频,始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案!
## 目录
- [官方文档](https://immich.app/docs/overview/introduction)
- [官方文档](https://immich.app/docs)
- [路线图](https://github.com/orgs/immich-app/projects/1)
- [示例](#示例)
- [功能特性](#功能特性)
- [介绍](https://immich.app/docs/overview/introduction)
- [安装](https://immich.app/docs/install/requirements)
- [贡献指南](https://immich.app/docs/overview/support-the-project)
- [支持本项目](#support-the-project)
- [已知问题](#known-issues)
- [支持本项目](#支持本项目)
## 官方文档
可以在 https://immich.app/ 找到包含安装手册的官方文档.
可以在 https://immich.app/ 找到官方文档(包含安装手册)。
## 示例
可以在 https://demo.immich.app 访问示例.
可以在 https://demo.immich.app 访问示例
在移动端, 可以使用 `https://demo.immich.app/api`获取`服务终端链接`
在移动端, 可以使用 `https://demo.immich.app/api` 获取 `服务终端链接`
```bash title="示例认证信息"
认证信息
@@ -60,57 +64,52 @@
```
```
规格: 甲骨文免费虚拟机套餐-阿姆斯特丹 4核 2.4Ghz ARM64 CPU, 24GB RAM。
规格: 甲骨文免费虚拟机套餐——阿姆斯特丹 4核 2.4Ghz ARM64 CPU, 24GB RAM。
```
# 功能特性
| 功能特性 | 移动端 | 网页端 |
| ------------------------------------------- | ------- | --- |
| 上传并查看照片和视频 | 是 | 是 |
| 软件运行时自动备份 | 是 | N/A |
| 上传并查看照片和视频 | 是 | 是 |
| 软件运行时自动备份 | 是 | N/A |
| 选择需要备份的相册 | 是 | N/A |
| 下载照片和视频到本地 | 是 | 是 |
| 下载照片和视频到本地 | 是 | 是 |
| 多用户支持 | 是 | 是 |
| 相册 | 是 | 是 |
| 共享相册 | 是 | 是 |
| 可拖动的快速导航栏 | 是 | 是 |
| 支持RAW格式 (HEIC, HEIF, DNG, Apple ProRaw) | 是 | 是 |
| 元数据视图 (EXIF, 地图) | 是 | 是 |
| 通过元数据、对象和标签进行搜索 | 是 | No |
| 管理功能 (用户管理) | N/A | 是 |
| 后台备份 | Android | N/A |
| 元数据视图EXIF, 地图 | 是 | 是 |
| 通过元数据、对象和标签进行搜索 | 是 | |
| 管理功能用户管理 | | 是 |
| 后台备份 | | N/A |
| 虚拟滚动 | 是 | 是 |
| OAuth支持 | 是 | 是 |
| 实时照片备份和查看 (仅iOS) | 是 | 是 |
| OAuth 支持 | 是 | 是 |
| API Keys|N/A|是|
| 实况照片备份和查看 | 仅 iOS | 是 |
|用户自定义存储结构|是|是|
|公共分享|否|是|
|归档与收藏功能|是|是|
|全局地图|否|是|
|好友分享|是|是|
|人像识别与分组|是|是|
|回忆(那年今日)|是|是|
|离线支持|是|否|
|只读相册|是|是|
# 支持本项目
我已经致力于本项目并且将我会持续更新文档、新增功能和修复问题。但是我不能一个人走下去,所以我需要给予我下去的动力。
我已经致力于本项目并且将我会持续更新文档、新增功能和修复问题。但是独木不成林,我需要给予我坚持下去的动力。
就像我主页里面 [selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) 说的一样,这是我和团队的一项艰巨任务。我希望某一天我能够全职开发本项目,在此我希望你们能够助我梦想成真。
就像我 [selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) 节目里说的一样,这是我和团队的一项艰巨任务。并且我希望某一天我能够全职开发本项目,在此我请求您能够助我梦想成真。
如果使用了本项目一段时间,并且觉得上面的话有道理,那么请你按照如下方式帮助我吧。
如果使用了本项目一段时间,并且觉得上面的话有道理,那么请您考虑通过下列任一方式支持我吧。
## 捐赠
- [按月捐赠](https://github.com/sponsors/alextran1502) via GitHub Sponsors
- [一次捐赠](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via Github Sponsors
# 已知问题
## TensorFlow 构建问题
_这是一个针对于Proxmox的已知问题_
TensorFlow 不能运行在很旧的CPU架构上, 需要运行在AVX和AVX2指令集的CPU上。如果你在docker-compose的命令行中遇到了 `illegal instruction core dump`的错误, 通过如下命令检查你的CPU flag寄存器然后确保你能够看到`AVX`和`AVX2`的字样:
```bash
more /proc/cpuinfo | grep flags
```
如果你在Proxmox中运行虚拟机, 虚拟机中没有启用flag寄存器。
你需要在虚拟机的硬件面板中把CPU类型从`kvm64`改为`host`。
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
- 通过 GitHub Sponsors [按月捐赠](https://github.com/sponsors/alextran1502)
- 通过 Github Sponsors [次捐赠](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502)
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- 比特币: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX

20
cli/.editorconfig Normal file
View File

@@ -0,0 +1,20 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[*.{ts,js}]
quote_type = single
[*.{md,mdx}]
max_line_length = off
trim_trailing_whitespace = false
[*.{yml,yaml}]
quote_type = double

1
cli/.eslintignore Normal file
View File

@@ -0,0 +1 @@
/dist

23
cli/.eslintrc.js Normal file
View File

@@ -0,0 +1,23 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': 0,
},
};

13
cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
*-debug.log
*-error.log
/.nyc_output
/dist
/lib
/tmp
/yarn.lock
node_modules
oclif.manifest.json
.vscode
.idea
/coverage/

18
cli/.prettierignore Normal file
View File

@@ -0,0 +1,18 @@
.DS_Store
node_modules
/build
/package
.env
.env.*
!.env.example
src/api/open-api
*.md
*.json
coverage
dist
**/migrations/**
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

6
cli/.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"semi": true
}

46
cli/README.md Normal file
View File

@@ -0,0 +1,46 @@
A command-line interface for interfacing with Immich
# Getting started
$ ts-node cli/src
To start using the CLI, you need to login with an API key first:
$ ts-node cli/src login-key https://your-immich-instance/api your-api-key
NOTE: This will store your api key under ~/.config/immich/auth.yml
Next, you can run commands:
$ ts-node cli/src server-info
When you're done, log out to remove the credentials from your filesystem
$ ts-node cli/src logout
# Usage
```
Usage: immich [options] [command]
Immich command line interface
Options:
-h, --help display help for command
Commands:
upload [options] [paths...] Upload assets
import [options] [paths...] Import existing assets
server-info Display server information
login-key [instanceUrl] [apiKey] Login using an API key
help [command] display help for command
```
# Todo
- Sidecar should check both .jpg.xmp and .xmp
- Sidecar check could be case-insensitive
# Known issues
- Upload can't use sdk due to multiple issues

6197
cli/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

68
cli/package.json Normal file
View File

@@ -0,0 +1,68 @@
{
"name": "immich-cli",
"dependencies": {
"axios": "^1.4.0",
"byte-size": "^8.1.1",
"cli-progress": "^3.12.0",
"commander": "^11.0.0",
"form-data": "^4.0.0",
"glob": "^10.3.1",
"picomatch": "^2.3.1",
"systeminformation": "^5.18.4",
"yaml": "^2.3.1"
},
"devDependencies": {
"@types/byte-size": "^8.1.0",
"@types/chai": "^4.3.5",
"@types/cli-progress": "^3.11.0",
"@types/jest": "^29.5.2",
"@types/js-yaml": "^4.0.5",
"@types/mime-types": "^2.1.1",
"@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.48.1",
"chai": "^4.3.7",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-unicorn": "^47.0.0",
"jest": "^29.5.0",
"jest-extended": "^4.0.0",
"jest-message-util": "^29.5.0",
"jest-mock-axios": "^4.7.2",
"jest-when": "^3.5.2",
"mock-fs": "^5.2.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"tslib": "^2.5.3",
"typescript": "^4.9.4"
},
"scripts": {
"build": "tsc --project tsconfig.build.json",
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
"prepack": "yarn build ",
"test": "jest",
"test:cov": "jest --coverage",
"format": "prettier --check ."
},
"jest": {
"clearMocks": true,
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": ".",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.ts$": "ts-jest"
},
"collectCoverageFrom": [
"<rootDir>/src/**/*.(t|j)s"
],
"coverageDirectory": "./coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,3 @@
// ./__mocks__/axios.js
import mockAxios from 'jest-mock-axios';
export default mockAxios;

50
cli/src/api/client.ts Normal file
View File

@@ -0,0 +1,50 @@
import {
AlbumApi,
APIKeyApi,
AssetApi,
AuthenticationApi,
Configuration,
JobApi,
OAuthApi,
ServerInfoApi,
SystemConfigApi,
UserApi,
} from './open-api';
import { ApiConfiguration } from '../cores/api-configuration';
export class ImmichApi {
public userApi: UserApi;
public albumApi: AlbumApi;
public assetApi: AssetApi;
public authenticationApi: AuthenticationApi;
public oauthApi: OAuthApi;
public serverInfoApi: ServerInfoApi;
public jobApi: JobApi;
public keyApi: APIKeyApi;
public systemConfigApi: SystemConfigApi;
private readonly config;
public readonly apiConfiguration: ApiConfiguration;
constructor(instanceUrl: string, apiKey: string) {
this.apiConfiguration = new ApiConfiguration(instanceUrl, apiKey);
this.config = new Configuration({
basePath: instanceUrl,
baseOptions: {
headers: {
'x-api-key': apiKey,
},
},
});
this.userApi = new UserApi(this.config);
this.albumApi = new AlbumApi(this.config);
this.assetApi = new AssetApi(this.config);
this.authenticationApi = new AuthenticationApi(this.config);
this.oauthApi = new OAuthApi(this.config);
this.serverInfoApi = new ServerInfoApi(this.config);
this.jobApi = new JobApi(this.config);
this.keyApi = new APIKeyApi(this.config);
this.systemConfigApi = new SystemConfigApi(this.config);
}
}

4
cli/src/api/open-api/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist

View File

@@ -0,0 +1 @@
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm

View File

@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@@ -0,0 +1,9 @@
.gitignore
.npmignore
.openapi-generator-ignore
api.ts
base.ts
common.ts
configuration.ts
git_push.sh
index.ts

View File

@@ -0,0 +1 @@
6.5.0

13633
cli/src/api/open-api/api.ts generated Normal file

File diff suppressed because it is too large Load Diff

72
cli/src/api/open-api/base.ts generated Normal file
View File

@@ -0,0 +1,72 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.75.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from './configuration';
// Some imports not used depending on template conditions
// @ts-ignore
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
export const BASE_PATH = "/api".replace(/\/+$/, "");
/**
*
* @export
*/
export const COLLECTION_FORMATS = {
csv: ",",
ssv: " ",
tsv: "\t",
pipes: "|",
};
/**
*
* @export
* @interface RequestArgs
*/
export interface RequestArgs {
url: string;
options: AxiosRequestConfig;
}
/**
*
* @export
* @class BaseAPI
*/
export class BaseAPI {
protected configuration: Configuration | undefined;
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
if (configuration) {
this.configuration = configuration;
this.basePath = configuration.basePath || this.basePath;
}
}
};
/**
*
* @export
* @class RequiredError
* @extends {Error}
*/
export class RequiredError extends Error {
constructor(public field: string, msg?: string) {
super(msg);
this.name = "RequiredError"
}
}

150
cli/src/api/open-api/common.ts generated Normal file
View File

@@ -0,0 +1,150 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.75.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from "./configuration";
import type { RequestArgs } from "./base";
import type { AxiosInstance, AxiosResponse } from 'axios';
import { RequiredError } from "./base";
/**
*
* @export
*/
export const DUMMY_BASE_URL = 'https://example.com'
/**
*
* @throws {RequiredError}
* @export
*/
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
if (paramValue === null || paramValue === undefined) {
throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
}
}
/**
*
* @export
*/
export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
if (configuration && configuration.apiKey) {
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
? await configuration.apiKey(keyParamName)
: await configuration.apiKey;
object[keyParamName] = localVarApiKeyValue;
}
}
/**
*
* @export
*/
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
if (configuration && (configuration.username || configuration.password)) {
object["auth"] = { username: configuration.username, password: configuration.password };
}
}
/**
*
* @export
*/
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
object["Authorization"] = "Bearer " + accessToken;
}
}
/**
*
* @export
*/
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
if (configuration && configuration.accessToken) {
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
? await configuration.accessToken(name, scopes)
: await configuration.accessToken;
object["Authorization"] = "Bearer " + localVarAccessTokenValue;
}
}
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
if (parameter == null) return;
if (typeof parameter === "object") {
if (Array.isArray(parameter)) {
(parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
}
else {
Object.keys(parameter).forEach(currentKey =>
setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
);
}
}
else {
if (urlSearchParams.has(key)) {
urlSearchParams.append(key, parameter);
}
else {
urlSearchParams.set(key, parameter);
}
}
}
/**
*
* @export
*/
export const setSearchParams = function (url: URL, ...objects: any[]) {
const searchParams = new URLSearchParams(url.search);
setFlattenedQueryParams(searchParams, objects);
url.search = searchParams.toString();
}
/**
*
* @export
*/
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
const nonString = typeof value !== 'string';
const needsSerialization = nonString && configuration && configuration.isJsonMime
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
: nonString;
return needsSerialization
? JSON.stringify(value !== undefined ? value : {})
: (value || "");
}
/**
*
* @export
*/
export const toPathString = function (url: URL) {
return url.pathname + url.search + url.hash
}
/**
*
* @export
*/
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
return axios.request<T, R>(axiosRequestArgs);
};
}

101
cli/src/api/open-api/configuration.ts generated Normal file
View File

@@ -0,0 +1,101 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.75.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface ConfigurationParameters {
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
username?: string;
password?: string;
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
basePath?: string;
baseOptions?: any;
formDataCtor?: new () => any;
}
export class Configuration {
/**
* parameter for apiKey security
* @param name security name
* @memberof Configuration
*/
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
/**
* parameter for basic security
*
* @type {string}
* @memberof Configuration
*/
username?: string;
/**
* parameter for basic security
*
* @type {string}
* @memberof Configuration
*/
password?: string;
/**
* parameter for oauth2 security
* @param name security name
* @param scopes oauth2 scope
* @memberof Configuration
*/
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
/**
* override base path
*
* @type {string}
* @memberof Configuration
*/
basePath?: string;
/**
* base options for axios calls
*
* @type {any}
* @memberof Configuration
*/
baseOptions?: any;
/**
* The FormData constructor that will be used to create multipart form data
* requests. You can inject this here so that execution environments that
* do not support the FormData class can still run the generated client.
*
* @type {new () => FormData}
*/
formDataCtor?: new () => any;
constructor(param: ConfigurationParameters = {}) {
this.apiKey = param.apiKey;
this.username = param.username;
this.password = param.password;
this.accessToken = param.accessToken;
this.basePath = param.basePath;
this.baseOptions = param.baseOptions;
this.formDataCtor = param.formDataCtor;
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* application/vnd.company+json
* @param mime - MIME (Multipurpose Internet Mail Extensions)
* @return True if the given MIME is JSON, false otherwise.
*/
public isJsonMime(mime: string): boolean {
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
}
}

View File

@@ -0,0 +1,57 @@
#!/bin/sh
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
#
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
git_user_id=$1
git_repo_id=$2
release_note=$3
git_host=$4
if [ "$git_host" = "" ]; then
git_host="github.com"
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
fi
if [ "$git_user_id" = "" ]; then
git_user_id="GIT_USER_ID"
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
fi
if [ "$git_repo_id" = "" ]; then
git_repo_id="GIT_REPO_ID"
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
fi
if [ "$release_note" = "" ]; then
release_note="Minor update"
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
fi
# Initialize the local directory as a Git repository
git init
# Adds the files in the local repository and stages them for commit.
git add .
# Commits the tracked changes and prepares them to be pushed to a remote repository.
git commit -m "$release_note"
# Sets the new remote
git_remote=$(git remote)
if [ "$git_remote" = "" ]; then # git remote not defined
if [ "$GIT_TOKEN" = "" ]; then
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
else
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
fi
fi
git pull origin master
# Pushes (Forces) the changes in the local repository up to the remote repository
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
git push origin master 2>&1 | grep -v 'To https'

18
cli/src/api/open-api/index.ts generated Normal file
View File

@@ -0,0 +1,18 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.75.1
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export * from "./api";
export * from "./configuration";

View File

@@ -0,0 +1,38 @@
import { ImmichApi } from '../api/client';
import path from 'node:path';
import { SessionService } from '../services/session.service';
import { LoginError } from '../cores/errors/login-error';
import { exit } from 'node:process';
import os from 'os';
import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api';
export abstract class BaseCommand {
protected sessionService!: SessionService;
protected immichApi!: ImmichApi;
protected deviceId!: string;
protected user!: UserResponseDto;
protected serverVersion!: ServerVersionResponseDto;
protected configDir;
protected authPath;
constructor() {
const userHomeDir = os.homedir();
this.configDir = path.join(userHomeDir, '.config/immich/');
this.sessionService = new SessionService(this.configDir);
this.authPath = path.join(this.configDir, 'auth.yml');
}
public async connect(): Promise<void> {
try {
this.immichApi = await this.sessionService.connect();
} catch (error) {
if (error instanceof LoginError) {
console.log(error.message);
exit(1);
} else {
throw error;
}
}
}
}

View File

@@ -0,0 +1,9 @@
import { BaseCommand } from '../../cli/base-command';
export default class LoginKey extends BaseCommand {
public async run(instanceUrl: string, apiKey: string): Promise<void> {
console.log('Executing API key auth flow...');
await this.sessionService.keyLogin(instanceUrl, apiKey);
}
}

View File

@@ -0,0 +1,13 @@
import { BaseCommand } from '../cli/base-command';
export default class Logout extends BaseCommand {
public static readonly description = 'Logout and remove persisted credentials';
public async run(): Promise<void> {
console.log('Executing logout flow...');
await this.sessionService.logout();
console.log('Successfully logged out');
}
}

View File

@@ -0,0 +1,15 @@
import { BaseCommand } from '../cli/base-command';
export default class ServerInfo extends BaseCommand {
static description = 'Display server information';
static enableJsonFlag = true;
public async run() {
console.log('Getting server information');
await this.connect();
const { data: versionInfo } = await this.immichApi.serverInfoApi.getServerVersion();
console.log(versionInfo);
}
}

175
cli/src/commands/upload.ts Normal file
View File

@@ -0,0 +1,175 @@
import { BaseCommand } from '../cli/base-command';
import { CrawledAsset } from '../cores/models/crawled-asset';
import { CrawlService, UploadService } from '../services';
import * as si from 'systeminformation';
import FormData from 'form-data';
import { UploadOptionsDto } from '../cores/dto/upload-options-dto';
import { CrawlOptionsDto } from '../cores/dto/crawl-options-dto';
import cliProgress from 'cli-progress';
import byteSize from 'byte-size';
export default class Upload extends BaseCommand {
private crawlService = new CrawlService();
private uploadService!: UploadService;
deviceId!: string;
uploadLength!: number;
dryRun = false;
public async run(paths: string[], options: UploadOptionsDto): Promise<void> {
await this.connect();
const uuid = await si.uuid();
this.deviceId = uuid.os || 'CLI';
this.uploadService = new UploadService(this.immichApi.apiConfiguration);
this.dryRun = options.dryRun;
const crawlOptions = new CrawlOptionsDto();
crawlOptions.pathsToCrawl = paths;
crawlOptions.recursive = options.recursive;
crawlOptions.excludePatterns = options.excludePatterns;
const crawledFiles: string[] = await this.crawlService.crawl(crawlOptions);
if (crawledFiles.length === 0) {
console.log('No assets found, exiting');
return;
}
const assetsToUpload = crawledFiles.map((path) => new CrawledAsset(path));
const uploadProgress = new cliProgress.SingleBar(
{
format: '{bar} | {percentage}% | ETA: {eta_formatted} | {value_formatted}/{total_formatted}: {filename}',
},
cliProgress.Presets.shades_classic,
);
let totalSize = 0;
let sizeSoFar = 0;
let totalSizeUploaded = 0;
let uploadCounter = 0;
for (const asset of assetsToUpload) {
// Compute total size first
await asset.process();
totalSize += asset.fileSize;
}
uploadProgress.start(totalSize, 0);
uploadProgress.update({ value_formatted: 0, total_formatted: byteSize(totalSize) });
for (const asset of assetsToUpload) {
uploadProgress.update({
filename: asset.path,
});
try {
if (options.import) {
const importData = {
assetPath: asset.path,
sidecarPath: asset.sidecarPath,
deviceAssetId: asset.deviceAssetId,
deviceId: this.deviceId,
fileCreatedAt: asset.fileCreatedAt,
fileModifiedAt: asset.fileModifiedAt,
isFavorite: false,
isReadOnly: options.readOnly,
};
if (!this.dryRun) {
await this.uploadService.import(importData);
}
} else {
await this.uploadAsset(asset, options.skipHash);
}
} catch (error) {
uploadProgress.stop();
throw error;
}
sizeSoFar += asset.fileSize;
if (!asset.skipped) {
totalSizeUploaded += asset.fileSize;
uploadCounter++;
}
uploadProgress.update(sizeSoFar, { value_formatted: byteSize(sizeSoFar) });
}
uploadProgress.stop();
let messageStart;
if (this.dryRun) {
messageStart = 'Would have ';
} else {
messageStart = 'Successfully ';
}
if (options.import) {
console.log(`${messageStart} imported ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`);
} else {
if (uploadCounter === 0) {
console.log('All assets were already uploaded, nothing to do.');
} else {
console.log(`${messageStart} uploaded ${uploadCounter} assets (${byteSize(totalSizeUploaded)})`);
}
if (options.delete) {
if (this.dryRun) {
console.log(`Would now have deleted assets, but skipped due to dry run`);
} else {
console.log('Deleting assets that have been uploaded...');
const deletionProgress = new cliProgress.SingleBar(cliProgress.Presets.shades_classic);
deletionProgress.start(crawledFiles.length, 0);
for (const asset of assetsToUpload) {
if (!this.dryRun) {
await asset.delete();
}
deletionProgress.increment();
}
deletionProgress.stop();
console.log('Deletion complete');
}
}
}
}
private async uploadAsset(asset: CrawledAsset, skipHash = false) {
await asset.readData();
let skipUpload = false;
if (!skipHash) {
const checksum = await asset.hash();
const checkResponse = await this.uploadService.checkIfAssetAlreadyExists(asset.path, checksum);
skipUpload = checkResponse.data.results[0].action === 'reject';
}
if (skipUpload) {
asset.skipped = true;
} else {
const uploadFormData = new FormData();
uploadFormData.append('deviceAssetId', asset.deviceAssetId);
uploadFormData.append('deviceId', this.deviceId);
uploadFormData.append('fileCreatedAt', asset.fileCreatedAt);
uploadFormData.append('fileModifiedAt', asset.fileModifiedAt);
uploadFormData.append('isFavorite', String(false));
uploadFormData.append('assetData', asset.assetData, { filename: asset.path });
if (asset.sidecarData) {
uploadFormData.append('sidecarData', asset.sidecarData, {
filename: asset.sidecarPath,
contentType: 'application/xml',
});
}
if (!this.dryRun) {
await this.uploadService.upload(uploadFormData);
}
}
}
}

View File

@@ -0,0 +1,9 @@
export class ApiConfiguration {
public readonly instanceUrl!: string;
public readonly apiKey!: string;
constructor(instanceUrl: string, apiKey: string) {
this.instanceUrl = instanceUrl;
this.apiKey = apiKey;
}
}

View File

@@ -0,0 +1,58 @@
// Check asset-upload.config.spec.ts for complete list
// TODO: we should get this list from the server via API in the future
// Videos
const videos = ['mp4', 'webm', 'mov', '3gp', 'avi', 'm2ts', 'mts', 'mpg', 'flv', 'mkv', 'wmv'];
// Images
const heic = ['heic', 'heif'];
const jpeg = ['jpg', 'jpeg'];
const png = ['png'];
const gif = ['gif'];
const tiff = ['tif', 'tiff'];
const webp = ['webp'];
const dng = ['dng'];
const other = [
'3fr',
'ari',
'arw',
'avif',
'cap',
'cin',
'cr2',
'cr3',
'crw',
'dcr',
'nef',
'erf',
'fff',
'iiq',
'jxl',
'k25',
'kdc',
'mrw',
'orf',
'ori',
'pef',
'raf',
'raw',
'rwl',
'sr2',
'srf',
'srw',
'orf',
'ori',
'x3f',
];
export const ACCEPTED_FILE_EXTENSIONS = [
...videos,
...jpeg,
...png,
...heic,
...gif,
...tiff,
...webp,
...dng,
...other,
];

View File

@@ -0,0 +1,6 @@
export class CrawlOptionsDto {
pathsToCrawl!: string[];
recursive = false;
includeHidden = false;
excludePatterns!: string[];
}

View File

@@ -0,0 +1,9 @@
export class UploadOptionsDto {
recursive = false;
excludePatterns!: string[];
dryRun = false;
skipHash = false;
delete = false;
import = false;
readOnly = true;
}

View File

@@ -0,0 +1,11 @@
export class LoginError extends Error {
constructor(message: string) {
super(message);
// assign the error class name in your custom error (as a shortcut)
this.name = this.constructor.name;
// capturing the stack trace keeps the reference to your error class
Error.captureStackTrace(this, this.constructor);
}
}

2
cli/src/cores/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './constants';
export * from './models';

View File

@@ -0,0 +1,58 @@
import * as fs from 'fs';
import { basename } from 'node:path';
import crypto from 'crypto';
export class CrawledAsset {
public path: string;
public assetData?: fs.ReadStream;
public deviceAssetId?: string;
public fileCreatedAt?: string;
public fileModifiedAt?: string;
public sidecarData?: Buffer;
public sidecarPath?: string;
public fileSize!: number;
public skipped = false;
constructor(path: string) {
this.path = path;
}
async readData() {
this.assetData = fs.createReadStream(this.path);
}
async process() {
const stats = await fs.promises.stat(this.path);
this.deviceAssetId = `${basename(this.path)}-${stats.size}`.replace(/\s+/g, '');
this.fileCreatedAt = stats.ctime.toISOString();
this.fileModifiedAt = stats.mtime.toISOString();
this.fileSize = stats.size;
// TODO: doesn't xmp replace the file extension? Will need investigation
const sideCarPath = `${this.path}.xmp`;
try {
fs.accessSync(sideCarPath, fs.constants.R_OK);
this.sidecarData = await fs.promises.readFile(sideCarPath);
this.sidecarPath = sideCarPath;
} catch (error) {}
}
async delete(): Promise<void> {
return fs.promises.unlink(this.path);
}
public async hash(): Promise<string> {
const sha1 = (filePath: string) => {
const hash = crypto.createHash('sha1');
return new Promise<string>((resolve, reject) => {
const rs = fs.createReadStream(filePath);
rs.on('error', reject);
rs.on('data', (chunk) => hash.update(chunk));
rs.on('end', () => resolve(hash.digest('hex')));
});
};
return await sha1(this.path);
}
}

View File

@@ -0,0 +1 @@
export * from './crawled-asset';

63
cli/src/index.ts Normal file
View File

@@ -0,0 +1,63 @@
import { program, Option } from 'commander';
import Upload from './commands/upload';
import ServerInfo from './commands/server-info';
import LoginKey from './commands/login/key';
program.name('immich').description('Immich command line interface');
program
.command('upload')
.description('Upload assets')
.usage('[options] [paths...]')
.addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false))
.addOption(new Option('-i, --ignore [paths...]', 'Paths to ignore').env('IMMICH_IGNORE_PATHS'))
.addOption(new Option('-h, --skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false))
.addOption(
new Option('-n, --dry-run', "Don't perform any actions, just show what will be done")
.env('IMMICH_DRY_RUN')
.default(false),
)
.addOption(new Option('--delete', 'Delete local assets after upload').env('IMMICH_DELETE_ASSETS'))
.argument('[paths...]', 'One or more paths to assets to be uploaded')
.action((paths, options) => {
options.excludePatterns = options.ignore;
new Upload().run(paths, options);
});
program
.command('import')
.description('Import existing assets')
.usage('[options] [paths...]')
.addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false))
.addOption(
new Option('-n, --dry-run', "Don't perform any actions, just show what will be done")
.env('IMMICH_DRY_RUN')
.default(false),
)
.addOption(new Option('-i, --ignore [paths...]', 'Paths to ignore').env('IMMICH_IGNORE_PATHS').default(false))
.addOption(new Option('--no-read-only', 'Import files without read-only protection, allowing Immich to manage them'))
.argument('[paths...]', 'One or more paths to assets to be imported')
.action((paths, options) => {
options.import = true;
options.excludePatterns = options.ignore;
new Upload().run(paths, options);
});
program
.command('server-info')
.description('Display server information')
.action(() => {
new ServerInfo().run();
});
program
.command('login-key')
.description('Login using an API key')
.argument('[instanceUrl]')
.argument('[apiKey]')
.action((paths, options) => {
new LoginKey().run(paths, options);
});
program.parse(process.argv);

View File

@@ -0,0 +1,235 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { CrawlService } from './crawl.service';
import mockfs from 'mock-fs';
import { toIncludeSameMembers } from 'jest-extended';
import { CrawlOptionsDto } from '../cores/dto/crawl-options-dto';
const matchers = require('jest-extended');
expect.extend(matchers);
const crawlService = new CrawlService();
describe('CrawlService', () => {
beforeAll(() => {
// Write a dummy output before mock-fs to prevent some annoying errors
console.log();
});
it('should crawl a single directory', async () => {
mockfs({
'/photos/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a single file', async () => {
mockfs({
'/photos/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/image.jpg'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a file and a directory', async () => {
mockfs({
'/photos/image.jpg': '',
'/images/photo.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/image.jpg', '/images/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg', '/images/photo.jpg']);
});
it('should exclude by file extension', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.tif': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.excludePatterns = ['**/*.tif'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should exclude by file extension without case sensitivity', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.tif': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.excludePatterns = ['**/*.TIF'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should exclude by folder', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/raw/image.jpg': '',
'/photos/raw2/image.jpg': '',
'/photos/folder/raw/image.jpg': '',
'/photos/crawl/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.excludePatterns = ['**/raw/**'];
options.recursive = true;
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg', '/photos/raw2/image.jpg', '/photos/crawl/image.jpg']);
});
it('should crawl multiple paths', async () => {
mockfs({
'/photos/image1.jpg': '',
'/images/image2.jpg': '',
'/albums/image3.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/', '/images/', '/albums/'];
options.recursive = false;
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image1.jpg', '/images/image2.jpg', '/albums/image3.jpg']);
});
it('should crawl a single path without trailing slash', async () => {
mockfs({
'/photos/image.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a single path without recursion', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/subfolder/image1.jpg': '',
'/photos/subfolder/image2.jpg': '',
'/image1.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should crawl a single path with recursion', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/subfolder/image1.jpg': '',
'/photos/subfolder/image2.jpg': '',
'/image1.jpg': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
options.recursive = true;
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers([
'/photos/image.jpg',
'/photos/subfolder/image1.jpg',
'/photos/subfolder/image2.jpg',
]);
});
it('should filter file extensions', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.txt': '',
'/photos/1': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers(['/photos/image.jpg']);
});
it('should include photo and video extensions', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.jpeg': '',
'/photos/image.heic': '',
'/photos/image.heif': '',
'/photos/image.png': '',
'/photos/image.gif': '',
'/photos/image.tif': '',
'/photos/image.tiff': '',
'/photos/image.webp': '',
'/photos/image.dng': '',
'/photos/image.nef': '',
'/videos/video.mp4': '',
'/videos/video.mov': '',
'/videos/video.webm': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/', '/videos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers([
'/photos/image.jpg',
'/photos/image.jpeg',
'/photos/image.heic',
'/photos/image.heif',
'/photos/image.png',
'/photos/image.gif',
'/photos/image.tif',
'/photos/image.tiff',
'/photos/image.webp',
'/photos/image.dng',
'/photos/image.nef',
'/videos/video.mp4',
'/videos/video.mov',
'/videos/video.webm',
]);
});
it('should check file extensions without case sensitivity', async () => {
mockfs({
'/photos/image.jpg': '',
'/photos/image.Jpg': '',
'/photos/image.jpG': '',
'/photos/image.JPG': '',
'/photos/image.jpEg': '',
'/photos/image.TIFF': '',
'/photos/image.tif': '',
'/photos/image.dng': '',
'/photos/image.NEF': '',
});
const options = new CrawlOptionsDto();
options.pathsToCrawl = ['/photos/'];
const paths: string[] = await crawlService.crawl(options);
expect(paths).toIncludeSameMembers([
'/photos/image.jpg',
'/photos/image.Jpg',
'/photos/image.jpG',
'/photos/image.JPG',
'/photos/image.jpEg',
'/photos/image.TIFF',
'/photos/image.tif',
'/photos/image.dng',
'/photos/image.NEF',
]);
});
afterEach(() => {
mockfs.restore();
});
});

View File

@@ -0,0 +1,47 @@
import { CrawlOptionsDto } from 'src/cores/dto/crawl-options-dto';
import { ACCEPTED_FILE_EXTENSIONS } from '../cores';
import { glob } from 'glob';
import * as fs from 'fs';
export class CrawlService {
public async crawl(crawlOptions: CrawlOptionsDto): Promise<string[]> {
const pathsToCrawl: string[] = crawlOptions.pathsToCrawl;
const directories: string[] = [];
const crawledFiles: string[] = [];
for await (const currentPath of pathsToCrawl) {
const stats = await fs.promises.stat(currentPath);
if (stats.isFile() || stats.isSymbolicLink()) {
crawledFiles.push(currentPath);
} else {
directories.push(currentPath);
}
}
let searchPattern: string;
if (directories.length === 1) {
searchPattern = directories[0];
} else if (directories.length === 0) {
return crawledFiles;
} else {
searchPattern = '{' + directories.join(',') + '}';
}
if (crawlOptions.recursive) {
searchPattern = searchPattern + '/**/';
}
searchPattern = `${searchPattern}/*.{${ACCEPTED_FILE_EXTENSIONS.join(',')}}`;
const globbedFiles = await glob(searchPattern, {
nocase: true,
nodir: true,
ignore: crawlOptions.excludePatterns,
});
const returnedFiles = crawledFiles.concat(globbedFiles);
returnedFiles.sort();
return returnedFiles;
}
}

View File

@@ -0,0 +1,2 @@
export * from './upload.service';
export * from './crawl.service';

View File

@@ -0,0 +1,95 @@
import { SessionService } from './session.service';
import mockfs from 'mock-fs';
import fs from 'node:fs';
import yaml from 'yaml';
import { LoginError } from '../cores/errors/login-error';
const mockPingServer = jest.fn(() => Promise.resolve({ data: { res: 'pong' } }));
const mockUserInfo = jest.fn(() => Promise.resolve({ data: { email: 'admin@example.com' } }));
jest.mock('../api/open-api', () => {
return {
__esModule: true,
...jest.requireActual('../api/open-api'),
UserApi: jest.fn().mockImplementation(() => {
return { getMyUserInfo: mockUserInfo };
}),
ServerInfoApi: jest.fn().mockImplementation(() => {
return { pingServer: mockPingServer };
}),
};
});
describe('SessionService', () => {
let sessionService: SessionService;
beforeAll(() => {
// Write a dummy output before mock-fs to prevent some annoying errors
console.log();
});
beforeEach(() => {
const configDir = '/config';
sessionService = new SessionService(configDir);
});
it('should connect to immich', async () => {
mockfs({
'/config/auth.yml': 'apiKey: pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg\ninstanceUrl: https://test/api',
});
await sessionService.connect();
expect(mockPingServer).toHaveBeenCalledTimes(1);
});
it('should error if no auth file exists', async () => {
mockfs();
await sessionService.connect().catch((error) => {
expect(error.message).toEqual('No auth file exist. Please login first');
});
});
it('should error if auth file is missing instance URl', async () => {
mockfs({
'/config/auth.yml': 'foo: pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg\napiKey: https://test/api',
});
await sessionService.connect().catch((error) => {
expect(error).toBeInstanceOf(LoginError);
expect(error.message).toEqual('Instance URL missing in auth config file /config/auth.yml');
});
});
it('should error if auth file is missing api key', async () => {
mockfs({
'/config/auth.yml': 'instanceUrl: pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg\nbar: https://test/api',
});
await sessionService.connect().catch((error) => {
expect(error).toBeInstanceOf(LoginError);
expect(error.message).toEqual('API key missing in auth config file /config/auth.yml');
});
});
it('should create auth file when logged in', async () => {
mockfs();
await sessionService.keyLogin('https://test/api', 'pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg');
const data: string = await fs.promises.readFile('/config/auth.yml', 'utf8');
const authConfig = yaml.parse(data);
expect(authConfig.instanceUrl).toBe('https://test/api');
expect(authConfig.apiKey).toBe('pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg');
});
it('should delete auth file when logging out', async () => {
mockfs({
'/config/auth.yml': 'apiKey: pNussssKSYo5WasdgalvKJ1n9kdvaasdfbluPg\ninstanceUrl: https://test/api',
});
await sessionService.logout();
await fs.promises.access('/auth.yml', fs.constants.F_OK).catch((error) => {
expect(error.message).toContain('ENOENT');
});
});
afterEach(() => {
mockfs.restore();
});
});

View File

@@ -0,0 +1,81 @@
import fs from 'node:fs';
import yaml from 'yaml';
import path from 'node:path';
import { ImmichApi } from '../api/client';
import { LoginError } from '../cores/errors/login-error';
export class SessionService {
readonly configDir: string;
readonly authPath!: string;
private api!: ImmichApi;
constructor(configDir: string) {
this.configDir = configDir;
this.authPath = path.join(this.configDir, 'auth.yml');
}
public async connect(): Promise<ImmichApi> {
await fs.promises.access(this.authPath, fs.constants.F_OK).catch((error) => {
if (error.code === 'ENOENT') {
throw new LoginError('No auth file exist. Please login first');
}
});
const data: string = await fs.promises.readFile(this.authPath, 'utf8');
const parsedConfig = yaml.parse(data);
const instanceUrl: string = parsedConfig.instanceUrl;
const apiKey: string = parsedConfig.apiKey;
if (!instanceUrl) {
throw new LoginError('Instance URL missing in auth config file ' + this.authPath);
}
if (!apiKey) {
throw new LoginError('API key missing in auth config file ' + this.authPath);
}
this.api = new ImmichApi(instanceUrl, apiKey);
await this.ping();
return this.api;
}
public async keyLogin(instanceUrl: string, apiKey: string): Promise<ImmichApi> {
this.api = new ImmichApi(instanceUrl, apiKey);
// Check if server and api key are valid
const { data: userInfo } = await this.api.userApi.getMyUserInfo().catch((error) => {
throw new LoginError(`Failed to connect to the server: ${error.message}`);
});
console.log(`Logged in as ${userInfo.email}`);
if (!fs.existsSync(this.configDir)) {
// Create config folder if it doesn't exist
fs.mkdirSync(this.configDir, { recursive: true });
}
fs.writeFileSync(this.authPath, yaml.stringify({ instanceUrl, apiKey }));
console.log('Wrote auth info to ' + this.authPath);
return this.api;
}
public async logout(): Promise<void> {
if (fs.existsSync(this.authPath)) {
fs.unlinkSync(this.authPath);
console.log('Removed auth file ' + this.authPath);
}
}
private async ping(): Promise<void> {
const { data: pingResponse } = await this.api.serverInfoApi.pingServer().catch((error) => {
throw new Error(`Failed to connect to the server: ${error.message}`);
});
if (pingResponse.res !== 'pong') {
throw new Error('Unexpected ping reply');
}
}
}

View File

@@ -0,0 +1,35 @@
import { UploadService } from './upload.service';
import mockfs from 'mock-fs';
import axios from 'axios';
import mockAxios from 'jest-mock-axios';
import FormData from 'form-data';
import { ApiConfiguration } from '../cores/api-configuration';
describe('UploadService', () => {
let uploadService: UploadService;
beforeAll(() => {
// Write a dummy output before mock-fs to prevent some annoying errors
console.log();
});
beforeEach(() => {
const apiConfiguration = new ApiConfiguration('https://example.com/api', 'key');
uploadService = new UploadService(apiConfiguration);
});
it('should upload a single file', async () => {
const data = new FormData();
uploadService.upload(data);
mockAxios.mockResponse();
expect(axios).toHaveBeenCalled();
});
afterEach(() => {
mockfs.restore();
mockAxios.reset();
});
});

View File

@@ -0,0 +1,65 @@
import axios, { AxiosRequestConfig } from 'axios';
import FormData from 'form-data';
import { ApiConfiguration } from '../cores/api-configuration';
export class UploadService {
private readonly uploadConfig: AxiosRequestConfig<any>;
private readonly checkAssetExistenceConfig: AxiosRequestConfig<any>;
private readonly importConfig: AxiosRequestConfig<any>;
constructor(apiConfiguration: ApiConfiguration) {
this.uploadConfig = {
method: 'post',
maxRedirects: 0,
url: `${apiConfiguration.instanceUrl}/asset/upload`,
headers: {
'x-api-key': apiConfiguration.apiKey,
},
maxContentLength: Number.POSITIVE_INFINITY,
maxBodyLength: Number.POSITIVE_INFINITY,
};
this.importConfig = {
method: 'post',
maxRedirects: 0,
url: `${apiConfiguration.instanceUrl}/asset/import`,
headers: {
'x-api-key': apiConfiguration.apiKey,
'Content-Type': 'application/json',
},
maxContentLength: Number.POSITIVE_INFINITY,
maxBodyLength: Number.POSITIVE_INFINITY,
};
this.checkAssetExistenceConfig = {
method: 'post',
maxRedirects: 0,
url: `${apiConfiguration.instanceUrl}/asset/bulk-upload-check`,
headers: {
'x-api-key': apiConfiguration.apiKey,
'Content-Type': 'application/json',
},
};
}
public checkIfAssetAlreadyExists(path: string, checksum: string): Promise<any> {
this.checkAssetExistenceConfig.data = JSON.stringify({ assets: [{ id: path, checksum: checksum }] });
// TODO: retry on 500 errors?
return axios(this.checkAssetExistenceConfig);
}
public upload(data: FormData): Promise<any> {
this.uploadConfig.data = data;
// TODO: retry on 500 errors?
return axios(this.uploadConfig);
}
public import(data: any): Promise<any> {
this.importConfig.data = data;
// TODO: retry on 500 errors?
return axios(this.importConfig);
}
}

3
cli/testSetup.js Normal file
View File

@@ -0,0 +1,3 @@
// add all jest-extended matchers
import * as matchers from 'jest-extended';
expect.extend(matchers);

4
cli/tsconfig.build.json Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "node_modules", "upload", "test", "**/*spec.ts"]
}

25
cli/tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"module": "commonjs",
"strict": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2017",
"moduleResolution": "node16",
"sourceMap": true,
"outDir": "./dist",
"incremental": true,
"skipLibCheck": true,
"esModuleInterop": true,
"baseUrl": "./",
"paths": {
"@test": ["test"],
"@test/*": ["test/*"]
}
},
"exclude": ["dist", "node_modules", "upload"]
}

View File

@@ -31,7 +31,6 @@ services:
build:
context: ../machine-learning
dockerfile: Dockerfile
command: python main.py
ports:
- 3003:3003
volumes:
@@ -43,11 +42,14 @@ services:
- NODE_ENV=development
depends_on:
- database
restart: always
restart: unless-stopped
immich-microservices:
container_name: immich_microservices
image: immich-microservices:latest
# extends:
# file: hwaccel.yml
# service: hwaccel
build:
context: ../server
dockerfile: Dockerfile
@@ -88,7 +90,7 @@ services:
volumes:
- ../web:/usr/src/app
- /usr/src/app/node_modules
restart: always
restart: unless-stopped
depends_on:
- immich-server
@@ -98,8 +100,8 @@ services:
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
logging:
driver: none
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
@@ -116,7 +118,6 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
PG_DATA: /var/lib/postgresql/data
volumes:
- pgdata:/var/lib/postgresql/data
ports:
@@ -137,7 +138,7 @@ services:
depends_on:
- immich-server
- immich-web
restart: always
restart: unless-stopped
volumes:
pgdata:

View File

@@ -0,0 +1,116 @@
version: "3.8"
services:
immich-server:
container_name: immich_server
image: immich-server:latest
build:
context: ../server
dockerfile: Dockerfile
command: ["./start-server.sh"]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
depends_on:
- redis
- database
- typesense
immich-machine-learning:
container_name: immich_machine_learning
image: immich-machine-learning:latest
build:
context: ../machine-learning
dockerfile: Dockerfile
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- model-cache:/cache
env_file:
- .env
restart: always
immich-microservices:
container_name: immich_microservices
image: immich-microservices:latest
# extends:
# file: hwaccel.yml
# service: hwaccel
build:
context: ../server
dockerfile: Dockerfile
command: ["./start-microservices.sh"]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
depends_on:
- database
- immich-server
- typesense
restart: always
immich-web:
container_name: immich_web
image: immich-web:latest
build:
context: ../web
dockerfile: Dockerfile
env_file:
- .env
restart: always
depends_on:
- immich-server
typesense:
container_name: immich_typesense
image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
restart: always
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:70a7a5b641117670beae0d80658430853896b5ef269ccf00d1827427e3263fa3
restart: always
database:
container_name: immich_postgres
image: postgres:14-alpine@sha256:28407a9961e76f2d285dc6991e8e48893503cc3836a4755bbc2d40bcc272a441
env_file:
- .env
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
volumes:
- pgdata:/var/lib/postgresql/data
restart: always
immich-proxy:
container_name: immich_proxy
image: immich-proxy:latest
environment:
# Make sure these values get passed through from the env file
- IMMICH_SERVER_URL
- IMMICH_WEB_URL
build:
context: ../nginx
dockerfile: Dockerfile
ports:
- 2283:8080
logging:
driver: none
depends_on:
- immich-server
restart: always
volumes:
pgdata:
model-cache:
tsdata:

View File

@@ -37,7 +37,6 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
PG_DATA: /var/lib/postgresql/data
volumes:
- /var/lib/postgresql/data
networks:

View File

@@ -18,6 +18,9 @@ services:
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends:
# file: hwaccel.yml
# service: hwaccel
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
@@ -51,8 +54,8 @@ services:
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
logging:
driver: none
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
restart: always
@@ -71,7 +74,6 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
PG_DATA: /var/lib/postgresql/data
volumes:
- pgdata:/var/lib/postgresql/data
restart: always

View File

@@ -1,116 +1,19 @@
###################################################################################
# Database
###################################################################################
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
# NOTE: The following four database variables support Docker secrets by adding a *_FILE suffix to the variable name
# See the docker-compose documentation on secrets for additional details: https://docs.docker.com/compose/compose-file/compose-file-v3/#secrets
# The location where your uploaded files are stored
UPLOAD_LOCATION=./library
# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release
# Connection secrets for postgres and typesense. You should change these to random passwords
TYPESENSE_API_KEY=some-random-text
DB_PASSWORD=postgres
# The values below this line do not need to be changed
###################################################################################
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE_NAME=immich
# Optional Database settings:
# DB_PORT=5432
###################################################################################
# Redis
###################################################################################
REDIS_HOSTNAME=immich_redis
# REDIS_URL will be used to pass custom options to ioredis.
# Example for Sentinel
# {"sentinels":[{"host":"redis-sentinel-node-0","port":26379},{"host":"redis-sentinel-node-1","port":26379},{"host":"redis-sentinel-node-2","port":26379}],"name":"redis-sentinel"}
# REDIS_URL=ioredis://eyJzZW50aW5lbHMiOlt7Imhvc3QiOiJyZWRpcy1zZW50aW5lbDEiLCJwb3J0IjoyNjM3OX0seyJob3N0IjoicmVkaXMtc2VudGluZWwyIiwicG9ydCI6MjYzNzl9XSwibmFtZSI6Im15bWFzdGVyIn0=
# Optional Redis settings:
# Note: these parameters are not automatically passed to the Redis Container
# to do so, please edit the docker-compose.yml file as well. Redis is not configured
# via environment variables, only redis.conf or the command line
# REDIS_PORT=6379
# REDIS_DBINDEX=0
# REDIS_USERNAME=
# REDIS_PASSWORD=
# REDIS_SOCKET=
###################################################################################
# Upload File Location
#
# This is the location where uploaded files are stored.
###################################################################################
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
###################################################################################
# Typesense
###################################################################################
TYPESENSE_API_KEY=some-random-text
# TYPESENSE_ENABLED=false
# TYPESENSE_URL uses base64 encoding for the nodes json.
# Example JSON that was used:
# [
# { "host": "typesense-1.example.net", "port": "443", "protocol": "https" },
# { "host": "typesense-2.example.net", "port": "443", "protocol": "https" },
# { "host": "typesense-3.example.net", "port": "443", "protocol": "https" },
# ]
# TYPESENSE_URL=ha://WwogIHsgImhvc3QiOiAidHlwZXNlbnNlLTEuZXhhbXBsZS5uZXQiLCAicG9ydCI6ICI0NDMiLCAicHJvdG9jb2wiOiAiaHR0cHMiIH0sCiAgeyAiaG9zdCI6ICJ0eXBlc2Vuc2UtMi5leGFtcGxlLm5ldCIsICJwb3J0IjogIjQ0MyIsICJwcm90b2NvbCI6ICJodHRwcyIgfSwKICB7ICJob3N0IjogInR5cGVzZW5zZS0zLmV4YW1wbGUubmV0IiwgInBvcnQiOiAiNDQzIiwgInByb3RvY29sIjogImh0dHBzIiB9Cl0=
###################################################################################
# Reverse Geocoding
#
# Reverse geocoding is done locally which has a small impact on memory usage
# This memory usage can be altered by changing the REVERSE_GEOCODING_PRECISION variable
# This ranges from 0-3 with 3 being the most precise
# 3 - Cities > 500 population: ~200MB RAM
# 2 - Cities > 1000 population: ~150MB RAM
# 1 - Cities > 5000 population: ~80MB RAM
# 0 - Cities > 15000 population: ~40MB RAM
####################################################################################
# DISABLE_REVERSE_GEOCODING=false
# REVERSE_GEOCODING_PRECISION=3
####################################################################################
# WEB - Optional
#
# Custom message on the login page, should be written in HTML form.
# For example:
# PUBLIC_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
####################################################################################
PUBLIC_LOGIN_PAGE_MESSAGE=
####################################################################################
# Alternative Service Addresses - Optional
#
# This is an advanced feature for users who may be running their immich services on different hosts.
# It will not change which address or port that services bind to within their containers, but it will change where other services look for their peers.
# Note: immich-microservices is bound to 3002, but no references are made
####################################################################################
IMMICH_WEB_URL=http://immich-web:3000
IMMICH_SERVER_URL=http://immich-server:3001
IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003
####################################################################################
# Alternative API's External Address - Optional
#
# This is an advanced feature used to control the public server endpoint returned to clients during Well-known discovery.
# You should only use this if you want mobile apps to access the immich API over a custom URL. Do not include trailing slash.
# NOTE: At this time, the web app will not be affected by this setting and will continue to use the relative path: /api
# Examples: http://localhost:3001, http://immich-api.example.com, etc
####################################################################################
#IMMICH_API_URL_EXTERNAL=http://localhost:3001
###################################################################################
# Immich Version - Optional
#
# This allows all immich docker images to be pinned to a specific version. By default,
# the version is "release" but could be a specific version, like "v1.59.0".
###################################################################################
#IMMICH_VERSION=

23
docker/hwaccel.yml Normal file
View File

@@ -0,0 +1,23 @@
version: "3.8"
# Hardware acceleration for transcoding - Optional
# This is only needed if you want to use hardware acceleration for transcoding.
# Depending on your hardware, you should uncomment the relevant lines below.
services:
hwaccel:
# devices:
# - /dev/dri:/dev/dri # If using Intel QuickSync or VAAPI
# volumes:
# - /usr/lib/wsl:/usr/lib/wsl # If using VAAPI in WSL2
# environment:
# - NVIDIA_DRIVER_CAPABILITIES=all # If using NVIDIA GPU
# - LD_LIBRARY_PATH=/usr/lib/wsl/lib # If using VAAPI in WSL2
# - LIBVA_DRIVER_NAME=d3d12 # If using VAAPI in WSL2
# deploy: # Uncomment this section if using NVIDIA GPU
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu,video]

View File

@@ -1,5 +1,5 @@
---
title: June 2023 update
title: Immich Update - June 2023
authors: [alextran]
tags: [update]
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,151 @@
---
title: Immich Update - July 2023
authors: [alextran]
tags: [update, v1.64.0-v1.71.0]
---
Hello, Immich fans, another month, another milestone. We hope you are staying cool and safe in this scorching hot summer across the globe.
Immich recently got some good recognition when getting to the front page of HackerNews, which helped to let more people know about the project's existence. The project will help more and more people find a solution to control the privacy of their most precious moments. And with the gain in popularity and recognition, we have gotten new users and more questions from the community than ever.
I want to express my gratitude to all the contributors and the community who have been tremendously helpful to new users' questions and provided technical support.
Below are the highlights of new features we added to the application over the past month, along with countless bug fixes and improvements across the board, from developer experience to resource optimization and UI/UX improvement. I hope you find these topics as exciting as I am.
## Highlights
- Memories feature.
- Facial recognition improvements.
- Improvements on multi selection behavior on the web.
- Shortcuts for common actions on the web.
- Support viewer for 360-panorama photos.
<!--truncate-->
---
### Memories feature
We've added the memory feature on the mobile app, so you can reminisce about your past memories.
<iframe
width="560"
height="315"
src="https://youtube.com/embed/c7OTl-RqNRE"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
### Facial recognition improvements
Over the past few releases, we have added many UI improvements to the facial recognition feature to help you manage the recognized people better. Some of the highlights:
#### Choose a new feature photo for a person.
<iframe
width="560"
height="315"
src="https://youtube.com/embed/PmJp8DmSh1U"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
#### Hide and show faces.
You can now select irrelevant faces to hide them. The hidden faces wont be displayed in search results and the people section in the info panel.
#### Merge faces.
This is useful when you have multiple faces of the same person in your photos, and you want to merge them into one.
<iframe
width="560"
height="315"
src="https://youtube.com/embed/-Xskhw-vpc4"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
We also added a nifty mechanism that when naming a face, similar names will prompt you a merge face option for the convenience.
<iframe
width="560"
height="315"
src="https://youtube.com/embed/XzE6wficbl4"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
### Improvements on multi selection behavior on the web
We have added a new multi selection behavior on the web to help you select multiple items easier. You can now select a range of photos and videos by holding the `Shift` key.
<iframe
width="560"
height="315"
src="https://youtube.com/embed/e_SiuHpVnmM"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
### Shortcuts for common actions on the web.
Some of us only navigate the world and the web with a keyboard (looking at you, Vim and Emacs users). So it would take away the sacred weapon of choice to require many clicks to perform repetitive actions. So we added quick shortcuts for the following action on the web.
<img
src={require('./images/web-shortcuts-panel.png').default}
width="100%"
style={{ borderRadius: '25px' }}
alt="Dot Env Example"
/>
### Support viewer for 360-panorama photos.
Photos with the EXIF property of `ProjectionType` will now have a special viewer on the web to view all the angles of the panorama.
The thumbnail of the 360 degrees panoramas will have a special icon on the top right of the thumbnail
<img
src="https://github.com/immich-app/immich/assets/61410067/728ca1b0-375c-4631-8081-a609843e702f"
width="50%"
style={{ borderRadius: '25px' }}
alt="Dot Env Example"
/>
Panorama in the detail view
<img
src="https://github.com/immich-app/immich/assets/61410067/3c89dac4-395d-45fa-9bc5-98a6248fd476"
width="50%"
style={{ borderRadius: '25px' }}
alt="Dot Env Example"
/>
---
Thank you, and I am asking for your support for the project. I hope to be a full-time maintainer of Immich one day to dedicate myself to the project as my life's work for the community and my family. You can find the support channels below:
- Monthly donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502)
- One-time donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502)
- [Liberapay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- Give a project a star - the contributors love gazing at the stars and seeing their creations shining in the sky.
Join our friendly [Discord](https://discord.gg/D8JsnBEuKb) to talk and discuss Immich, tech, or anything
Cheer!
Until next time!
Alex

View File

@@ -39,7 +39,7 @@ This often happens when using a reverse proxy or cloudflare tunnel in front of I
### Why is Immich slow on low-memory systems like the Raspberry Pi?
Immich uses optional machine-learning features to enhance search results. This feature, however, can be too heavy to run on a Raspberry Pi. To disable machine learning, comment out the `immich-machine-learning` section of your docker-compose.yml and set `IMMICH_MACHINE_LEARNING_URL=false` in your .env file.
Immich uses optional machine-learning features to enhance search results. This feature, however, can be too heavy to run on a Raspberry Pi. To disable machine learning, comment out the `immich-machine-learning` section of your docker-compose.yml and set `IMMICH_MACHINE_LEARNING_ENABLED=false` in your .env file.
### How to disable machine-learning and TypeSense?
@@ -47,7 +47,7 @@ Immich uses optional machine-learning features to enhance search results. This f
Disabling both will result in poor search experience and typesense utilizes CLIP embeddings which are generated by machine-learning.
:::
These features can be disabled by commenting out `immich-typesense` and `immich-machine-learning` sections of the docker-compose.yml and setting `IMMICH_MACHINE_LEARNING_URL=false` & `TYPESENSE_ENABLED=false` in your .env file.
These features can be disabled by commenting out `immich-typesense` and `immich-machine-learning` sections of the docker-compose.yml and setting `IMMICH_MACHINE_LEARNING_ENABLED=false` & `TYPESENSE_ENABLED=false` in your .env file.
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
@@ -94,8 +94,34 @@ To remove the **Metadata** you can stop Immich and delete the volume.
docker-compose down -v
```
After removing the the containers and volumes, the **Files** can be cleaned up (if necessary) from the `UPLOAD_LOCATION` by simply deleting an unwanted files or folders.
After removing the containers and volumes, the **Files** can be cleaned up (if necessary) from the `UPLOAD_LOCATION` by simply deleting an unwanted files or folders.
### Why iOS app shows duplicate photos on the timeline while the web doesn't?
If you are using `My Photo Stream`, the Photos app temporarily creates duplicates of photos taken in the last 30 days. These photos are included in the `Recents` album and thus shown up twice. To fix this, you can disable `My Photo Stream` in the native Photos app or choose a different album in the backup screen in Immich.
### How can I move all data (photos, persons, albums) from one user to another?
This requires some database queries. You can do this on the command line (in the PostgreSQL container using the psql command), or you can add for example an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file, so that you can use a web-interface.
:::warning
This is an advanced operation. If you can't to do it with the steps described here, this is not for you.
:::
1. **MAKE A BACKUP** - See [backup and restore](/docs/administration/backup-and-restore.md).
2. Find the id of both the 'source' and the 'destination' user (it's the id column in the users table)
3. Three tables need to be updated:
```sql
// reassign albums
update albums set "ownerId" = '<destinationId>' where "ownerId" = '<sourceId>';
// reassign people
update person set "ownerId" = '<destinationId>' where "ownerId" = '<sourceId>';
// reassign assets
update assets set "ownerId" = '<destinationId>' where "ownerId" = '<sourceId>'
and checksum not in (select checksum from assets where "ownerId" = '<destinationId>');
```
4. There might be left-over assets in the 'source' user's library if they are skipped by the last query because of duplicate checksums. These are probably duplicates anyway, and can probably be removed.

View File

@@ -2,6 +2,10 @@
## Database
:::caution
Immich saves [file paths in the database](https://github.com/immich-app/immich/discussions/3299), it does not scan the library folder to update the database so backups are crucial.
:::
:::info
Refer to the official [postgres documentation](https://www.postgresql.org/docs/current/backup.html) for details about backing up and restoring a postgres database.
:::

View File

@@ -15,7 +15,34 @@ While the reverse proxy provided by Immich works well for basic deployments, som
## Adding a Custom Reverse Proxy
Users can deploy a custom reverse proxy that forwards requests to Immich's reverse proxy. This way, the new reverse proxy can handle TLS termination, load balancing, or other advanced features, while still delegating routing decisions to Immich's reverse proxy. All reverse proxies between Immich and the user must forward all headers and set the `Host`, `X-Forwarded-Host`, `X-Forwarded-Proto` and `X-Forwarded-For` headers to their appropriate values. By following these practices, you ensure that all custom reverse proxies are fully compatible with Immich.
Users can deploy a custom reverse proxy that forwards requests to Immich's reverse proxy. This way, the new reverse proxy can handle TLS termination, load balancing, or other advanced features, while still delegating routing decisions to Immich's reverse proxy. All reverse proxies between Immich and the user must forward all headers and set the `Host`, `X-Forwarded-Host`, `X-Forwarded-Proto` and `X-Forwarded-For` headers to their appropriate values. Additionally, your reverse proxy should allow for big enough uploads. By following these practices, you ensure that all custom reverse proxies are fully compatible with Immich.
### Nginx example config
Below is an example config for nginx:
```nginx
server {
server_name <snip>
# https://github.com/immich-app/immich/blob/main/nginx/templates/default.conf.template#L28
client_max_body_size 50000M;
location / {
proxy_pass http://<snip>:2283;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# http://nginx.org/en/docs/http/websocket.html
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
}
}
```
## Replacing the Default Reverse Proxy

View File

@@ -1,6 +1,6 @@
# Server Commands
The `immich-server` docker image comes preinstalled with an administrative CLI (`immich`) that supports the following commands:
The `immich-server` docker image comes preinstalled with an administrative CLI (`immich-admin`) that supports the following commands:
| Command | Description |
| ------------------------ | ------------------------------------- |
@@ -12,10 +12,12 @@ The `immich-server` docker image comes preinstalled with an administrative CLI (
## How to run a command
To run a command, [connect](/docs/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich <command>`.
To run a command, [connect](/docs/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich-admin <command>`.
## Examples
Note that the commands below should begin with `immich-admin`.
Reset Admin Password
![Reset Admin Password](./img/reset-admin-password.png)

View File

@@ -4,38 +4,107 @@ sidebar_position: 1
# Architecture
Immich uses a traditional client-server design, with a dedicated database for data persistence. The frontend clients communicate with backend services over HTTP using REST APIs.
## High Level Diagram
![Immich Architecture](./img/app-architecture.png)
## Technology
The diagram shows clients communicating with the server via REST, as well as the flow of database between backend services.
Immich is a full-stack [TypeScript](https://www.typescriptlang.org/) application, with a [Flutter](https://flutter.dev/) mobile app.
## Clients
### Mobile
Immich has three main clients:
- [Flutter](https://flutter.dev/)
- [Riverpod](https://riverpod.dev/) for state management.
1. Mobile app - Android, iOS
2. Web app - Responsive website
3. CLI - Command-line utility for bulk upload
### Web
:::info
All three clients use [OpenAPI](./open-api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](./open-api.md).
:::
- [SvelteKit](https://kit.svelte.dev/)
- [Tailwindcss](https://tailwindcss.com/)
### Mobile App
### Server
The mobile app is written in [Flutter](https://flutter.dev/). It uses [Isar Database](https://isar.dev/) for a local database and [Riverpod](https://riverpod.dev/) for state management.
- [Node.js](https://nodejs.org/)
- [Nest.js](https://nestjs.com/)
- [TypeORM](https://typeorm.io/) for database management.
- [Jest](https://jestjs.io/) for testing.
- [Python](https://www.python.org/) for Machine Learning.
### Web Client
### Database
The web app is a [TypeScript](https://www.typescriptlang.org/) project that uses [SvelteKit](https://kit.svelte.dev) and [Tailwindcss](https://tailwindcss.com/).
- [PostgreSQL](https://www.postgresql.org/)
- [Redis](https://redis.io/) for job queuing.
- [Typesense](https://typesense.org/) for search.
### CLI
### Web Server
The CLI is a [TypeScript](https://www.typescriptlang.org/) project that parses command line arguments to programmatically upload/import assets to an Immich server. See [Bulk Upload](/docs/features/bulk-upload.md) for more information about its usage.
- [NGINX](https://www.nginx.com/) for internal communication between containers and load balancing when scaling.
## Server
The Immich backend is divided into several services, which are run as individual docker containers.
1. `immich-server` - Handle and respond to REST API requests
1. `immich-microservices` - Execute background jobs (thumbnail generation, metadata extraction, transcoding, etc.)
1. `immich-machine-learning` - Execute machine learning models
1. `postgres` - Persistent data storage
1. `redis`- Queue management for `immich-microservices`
1. `typesense`- Specialized database for search, specifically with vector comparison features
### Immich Server
The Immich Server is a [TypeScript](https://www.typescriptlang.org/) project written for [Node.js](https://nodejs.org/). It uses the [Nest.js](https://nestjs.com) framework, with [TypeORM](https://typeorm.io/) for database management. The server codebase also loosely follows the [Hexagonal Architecture](<https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)>). Specifically, we aim to separate technology specific implementations (`infra/`) from core business logic (`domain/`).
#### REST Endpoints
The server is a list of HTTP endpoints and associated handlers (controllers). Each controller usually implements the following CRUD operations:
- `POST` `/<type>` - **Create**
- `GET` `/<type>` - **Read** (all)
- `GET` `/<type>/:id` - **Read** (by id)
- `PUT` `/<type>/:id` - **Updated** (by id)
- `DELETE` `/<type>/:id` - **Delete** (by id)
#### DTOs
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client.
### Microservices
The Immich Microservices image uses the same `Dockerfile` as the Immich Server, but with a different entrypoint. The Immich Microservices service mainly handles executing jobs, which include the following:
- Thumbnail Generation
- Metadata Extraction
- Video Transcoding
- Object Tagging
- Facial Recognition
- Storage Template Migration
- Search (Typesense synchronization)
- Sidecar (see [XMP Sidecars](/docs/features/xmp-sidecars.md))
- Background jobs (file deletion, user deletion)
:::info
This list closely matches what is available on the [Administration > Jobs](/docs/administration/jobs.md) page, which provides some remote queue management capabilities.
:::
### Machine Learning
The machine learning service is written in [Python](https://www.python.org/) and uses [FastAPI](https://fastapi.tiangolo.com/) for HTTP communication.
All machine learning related operations have been externalized to this service, `immich-machine-learning`. Python is a natural choice for AI and machine learning. It also has some pretty specific hardware requirements. Running it as a separate container makes it possible to run the container on a separate machine, or easily disable it entirely.
Machine learning models are also quite _large_, requiring _quite a bit_ of memory. We are always looking for ways to improve and optimize this aspect of this container specifically.
### Postgres
Immich persists data in Postgres, which includes information about access and authorization, users, albums, asset, sharing settings, etc.
:::info
See [Database Migrations](./database-migrations.md) for more information about how to modify the database to create an index, modify a table, add a new column, etc.
:::
### Redis
Immich uses [Redis](https://redis.com/) via [BullMQ](https://docs.bullmq.io/) to manage job queues. Some jobs trigger subsequent jobs. For example, object detection relies on thumbnail generation and automatically run after one is generated.
### Typesense
Immich synchronizes some of the Postgres data into Typesense, so it can execute vector related queries in order to implement certain features including, facial recognition and CLIP search.
<!-- - [NGINX](https://www.nginx.com/) for internal communication between containers and load balancing when scaling. -->

View File

@@ -0,0 +1,22 @@
---
title: Directories
---
# Repository Folder Structure
Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](https://en.wikipedia.org/wiki/Monorepo) and includes the following folders:
| Folder | Description |
| :------------------ | :------------------------------------------------------------------- |
| `.github/` | Github templates and action workflows |
| `.vscode/` | VSCode debug launch profiles |
| `cli/` | Source code for the work-in-progress CLI rewrite |
| `docker/` | Docker compose resources for dev, test, production |
| `design/` | Screenshots and logos for the README |
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
| `machine-learning/` | Source code for the `immich-machine-learning` docker image |
| `misc/release/` | Scripts for version pumps and draft releases |
| `mobile/` | Source code for the mobile app, both Android and iOS |
| `nginx/` | Source code for the `immich-proxy` docker image |
| `server/` | Source code for the `immich-server` docker image |
| `web/` | Source code for the `immich-web` docker image |

View File

@@ -1,10 +1,10 @@
# Open API
# OpenAPI
Immich uses the [Open API](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](/docs/api).
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](/docs/api).
## Generator
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). When you add a new or modify an existing endpoint, you must run the command below to update the client SDK.
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server when running in development mode. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the command below to update the client SDK.
```bash
npm run api:generate # Run from the `server/` directory

View File

@@ -24,9 +24,9 @@ Run all web checks with `npm run check:all`
Run all server checks with `npm run check:all`
:::
## Open API
## OpenAPI
The Open API client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. See [Open API](/docs/developer/open-api.md) for more details.
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/docs/developer/open-api.md) for more details.
## Database Migrations

View File

@@ -15,6 +15,25 @@ You can use the CLI to upload an existing gallery to the Immich server
npm i -g immich
```
Pre-installed on the `immich-server` container and can be easily accessed through
```
immich
```
### Options
| Parameter | Description |
| ---------------- | ------------------------------------------------------------------- |
| --yes / -y | Assume yes on all interactive prompts |
| --recursive / -r | Include subfolders |
| --delete / -da | Delete local assets after upload |
| --key / -k | User's API key |
| --server / -s | Immich's server address |
| --threads / -t | Number of threads to use (Default 5) |
| --album/ -al | Create albums for assets based on the parent folder or a given name |
| --import/ -i | Import gallery (assets are not uploaded) |
## Quick Start
Specify user's credential, Immich's server address and port and the directory you would like to upload videos/photos from.
@@ -29,27 +48,16 @@ By default, subfolders are not included. To upload a directory including subfold
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive directory/
```
---
### Options
| Parameter | Description |
| ---------------- | ------------------------------------------------------------------- |
| --yes / -y | Assume yes on all interactive prompts |
| --recursive / -r | Include subfolders |
| --delete / -da | Delete local assets after upload |
| --key / -k | User's API key |
| --server / -s | Immich's server address |
| --threads / -t | Number of threads to use (Default 5) |
| --album/ -al | Create albums for assets based on the parent folder or a given name |
| --import/ -i | Import gallery |
### Obtain the API Key
The API key can be obtained in the user setting panel on the web interface.
![Obtain Api Key](./img/obtain-api-key.png)
---
## Uploading existing libraries
### Run via Docker
You can run the CLI inside of a docker container to avoid needing to install anything.
@@ -102,3 +110,70 @@ npm run build
```bash title="Run the command"
node bin/index.js upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive your/asset/directory
```
---
## Importing existing libraries
If you do not wish to upload files into the server, existing files can be imported into the immich gallery through the use of the `--import` flag.
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive directory/ --import
```
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api file1.jpg file2.jpg --import
```
The `immich-server` and `immich-microservices` containers must be able to access the files, or directories at the path referenced in the command. The directories referenced must be set under a user's `External Path` setting. More detailed instructions can be found [here](/docs/features/read-only-gallery).
:::tip Matching volume references
The import command is most easily run on the machine running the immich service, as the path to the files on the machine running the command and the server much match identically.
If you are running immich within docker, the volume pointing to your existing library should be identical with your host machine.
```diff title="docker-compose.yml"
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "immich" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /path/to/media:/path/to/media
env_file:
- .env
depends_on:
- redis
- database
- typesense
restart: always
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /path/to/media:/path/to/media
env_file:
- .env
depends_on:
- redis
- database
- typesense
restart: always
```
The proper command for above would be as shown below. You should have access to `/path/to/media` exactly on the environment the CLI command is being run on
```
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive /path/to/media --import
```
If you are running the import using the docker command, please note that the volumes should point to the `/path/to/media` exactly on the environment the CLI command is being run on
```
docker run -it --rm -v "/path/to/media:/path/to/media" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive /path/to/media --import
```
:::

View File

@@ -0,0 +1,60 @@
# Hardware Transcoding [Experimental]
This feature allows you to use a GPU or Intel Quick Sync to accelerate transcoding and reduce CPU load.
Note that hardware transcoding is much less efficient for file sizes.
As this is a new feature, it is still experimental and may not work on all systems.
## Supported APIs
- NVENC
- NVIDIA GPUs
- Quick Sync
- Intel CPUs
- VAAPI
- GPUs
## Limitations
- The instructions and configurations here are specific to Docker Compose. Other container engines may require different configuration.
- Only Linux and Windows (through WSL2) servers are supported.
- WSL2 does not support Quick Sync.
- Raspberry Pi is currently not supported.
- Two-pass mode is only supported for NVENC. Other APIs will ignore this setting.
- Only encoding is currently hardware accelerated, so the CPU is still used for software decoding.
- This is mainly because the original video may not be hardware-decodable.
- Hardware dependent
- Codec support varies, but H.264 and HEVC are usually supported.
- Notably, NVIDIA and AMD GPUs do not support VP9 encoding.
- Newer devices tend to have higher transcoding quality.
## Prerequisites
#### NVENC
- You must have the official NVIDIA driver installed on the server.
- On Linux (except for WSL2), you also need to have [NVIDIA Container Runtime][nvcr] installed.
#### QSV
- For VP9 to work:
- You must have a 9th gen Intel CPU or newer
- If you have an 11th gen CPU or older, then you may need to follow [these][jellyfin-lp] instructions as Low-Power mode is required
- Additionally, if the server specifically has an 11th gen CPU and is running kernel 5.15 (shipped with Ubuntu 22.04 LTS), then you will need to upgrade this kernel (from [Jellyfin docs][jellyfin-kernel-bug])
## Setup
1. If you do not already have it, download the latest [`hwaccel.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. Uncomment the lines that apply to your system and desired usage.
3. In the `docker-compose.yml` under `immich-microservices`, uncomment the lines relating to the `hwaccel.yml` file.
4. Redeploy the `immich-microservices` container with these updated settings.
5. In the Admin page under `FFmpeg settings`, change the hardware acceleration setting to the appropriate option and save.
## Tips
- You may want to choose a slower preset than for software transcoding to maintain quality and efficiency
- While you can use VAAPI with Nvidia GPUs and Intel CPUs, prefer the more specific APIs since they're more optimized for their respective devices
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.yml
[nvcr]: https://github.com/NVIDIA/nvidia-container-runtime/
[jellyfin-lp]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#configure-and-verify-lp-mode-on-linux
[jellyfin-kernel-bug]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#known-issues-and-limitations

View File

@@ -42,8 +42,8 @@ We will use those values in the steps below.
command: [ "start.sh", "immich" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/media/precious-memory:/mnt/media/precious-memory
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory
+ - /mnt/media/precious-memory:/mnt/media/precious-memory:ro
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory:ro
env_file:
- .env
depends_on:
@@ -58,8 +58,8 @@ We will use those values in the steps below.
command: [ "start.sh", "microservices" ]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /mnt/media/precious-memory:/mnt/media/precious-memory
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory
+ - /mnt/media/precious-memory:/mnt/media/precious-memory:ro
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory:ro
env_file:
- .env
depends_on:

View File

@@ -0,0 +1,26 @@
# Hosting the machine-learning service on another system
To alleviate [performance issues on low-memory systems](/docs/FAQ.md#why-is-immich-slow-on-low-memory-systems-like-the-raspberry-pi) like the Raspberry Pi, you may also host Immich's machine-learning container on a more powerful system (e.g. your laptop or desktop computer):
- Set `IMMICH_MACHINE_LEARNING_URL` to point to the designated ML system, e.g. `http://workstation:3003`.
- Copy the following `docker-compose.yml` to your ML system.
- Start the container by running `docker-compose up -d` or `docker compose up -d` (depending on your Docker version).
```yaml
version: '3.8'
services:
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- model-cache:/cache
restart: always
ports:
- 3003:3003
volumes:
model-cache:
```
Please note that version mismatches between both hosts may cause instabilities and bugs, so make sure to always perform updates together.

View File

@@ -0,0 +1,91 @@
# Config File
A config file can be provided as an alternative to the UI configuration.
### Step 1 - Create a new config file
In JSON format, create a new config file (e.g. `immich.config`) and put it in a location that can be accessed by Immich.
The default configuration looks like this:
```json
{
"ffmpeg": {
"crf": 23,
"threads": 0,
"preset": "ultrafast",
"targetVideoCodec": "h264",
"targetAudioCodec": "aac",
"targetResolution": "720",
"maxBitrate": "0",
"twoPass": false,
"transcode": "required",
"tonemap": "hable",
"accel": "disabled"
},
"job": {
"backgroundTask": {
"concurrency": 5
},
"clipEncoding": {
"concurrency": 2
},
"metadataExtraction": {
"concurrency": 5
},
"objectTagging": {
"concurrency": 2
},
"recognizeFaces": {
"concurrency": 2
},
"search": {
"concurrency": 5
},
"sidecar": {
"concurrency": 5
},
"storageTemplateMigration": {
"concurrency": 5
},
"thumbnailGeneration": {
"concurrency": 5
},
"videoConversion": {
"concurrency": 1
}
},
"oauth": {
"enabled": false,
"issuerUrl": "",
"clientId": "",
"clientSecret": "",
"mobileOverrideEnabled": false,
"mobileRedirectUri": "",
"scope": "openid email profile",
"storageLabelClaim": "preferred_username",
"buttonText": "Login with OAuth",
"autoRegister": true,
"autoLaunch": false
},
"passwordLogin": {
"enabled": true
},
"storageTemplate": {
"template": "{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}"
},
"thumbnail": {
"webpSize": 250,
"jpegSize": 1440
}
}
```
:::tip
In Administration > Settings is a button to copy the current configuration to your clipboard.
So you can just grab it from there, paste it into a file and you're pretty much good to go.
:::
### Step 2 - Specify the file location
In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config.
For more information, refer to the [Environment Variables](https://docs.immich.app/docs/install/environment-variables) section.

View File

@@ -25,10 +25,18 @@ wget https://github.com/immich-app/immich/releases/latest/download/docker-compos
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
```
```bash title="(Optional) Get hwaccel.yml file"
wget https://github.com/immich-app/immich/releases/latest/download/hwaccel.yml
```
or by downloading from your browser and moving the files to the directory that you created.
Note: If you downloaded the files from your browser, also ensure that you rename `example.env` to `.env`.
:::info
Optionally, you can use the [`hwaccel.yml`][hw-file] file to enable hardware acceleration for transcoding. See the [Hardware Transcoding](/docs/features/hardware-transcoding.md) guide for info on how to set this up.
:::
### Step 2 - Populate the .env file with custom values
<details>
@@ -124,7 +132,6 @@ PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server"
IMMICH_WEB_URL=http://immich-web:3000
IMMICH_SERVER_URL=http://immich-server:3001
IMMICH_MACHINE_LEARNING_URL=http://immich-machine-learning:3003
####################################################################################
# Alternative API's External Address - Optional
@@ -166,6 +173,10 @@ docker-compose up -d # or `docker compose up -d` based on your docker-compos
For more information on how to use the application, please refer to the [Post Installation](/docs/install/post-install.mdx) guide.
:::
:::tip
Note that downloading container images might require you to authenticate to the GitHub Container Registry ([steps here](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry)).
:::
### Step 4 - Upgrading
If `IMMICH_VERSION` is set, it will need to be updated to the latest or desired version.
@@ -182,4 +193,5 @@ Immich is currently under heavy development, which means you can expect breaking
[compose-file]: https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
[env-file]: https://github.com/immich-app/immich/releases/latest/download/example.env
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.yml
[watchtower]: https://containrrr.dev/watchtower/

View File

@@ -1,3 +1,7 @@
---
sidebar_position: 90
---
# Environment Variables
## Docker Compose
@@ -22,6 +26,7 @@ These environment variables are used by the `docker-compose.yml` file and do **N
| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices |
| `PUBLIC_LOGIN_PAGE_MESSAGE` | Public Login Page Message | | web |
| `IMMICH_CONFIG_FILE` | Path to config file | | server |
:::tip
@@ -50,13 +55,14 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## URLs
| Variable | Description | Default | Services |
| :---------------------------- | :------------------------------------------------------- | :-----------------------------------: | :-------------------- |
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, set `"false"` to disable ML | `http://immich-machine-learning:3003` | server, microservices |
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
| Variable | Description | Default | Services |
| :-------------------------------- | :--------------------------- | :-----------------------------------: | :-------------------- |
| `IMMICH_WEB_URL` | Immich Web URL | `http://immich-web:3000` | proxy |
| `IMMICH_SERVER_URL` | Immich Server URL | `http://immich-server:3001` | web, proxy |
| `IMMICH_MACHINE_LEARNING_ENABLED` | Enabled machine learning | `true` | server, microservices |
| `IMMICH_MACHINE_LEARNING_URL` | Immich Machine Learning URL, | `http://immich-machine-learning:3003` | server, microservices |
| `PUBLIC_IMMICH_SERVER_URL` | Public Immich URL | `http://immich-server:3001` | web |
| `IMMICH_API_URL_EXTERNAL` | Immich API URL External | `/api` | web |
:::info
@@ -184,3 +190,24 @@ Typesense URL example JSON before encoding:
| `MACHINE_LEARNING_CLASSIFICATION_MODEL` | Classification Model | `microsoft/resnet-50` | machine learning |
| `MACHINE_LEARNING_CACHE_FOLDER` | ML Cache Location | `/cache` | machine learning |
| `TRANSFORMERS_CACHE` | ML Transformers Cache Location | `/cache` | machine learning |
## Docker Secrets
The following variables support the use of [Docker secrets](https://docs.docker.com/engine/swarm/secrets/) for additional security.
To use any of these, replace the regular environment variable with the equivalent `_FILE` environment variable. The value of
the `_FILE` variable should be set to the path of a file containing the variable value.
| Regular Variable | Equivalent Docker Secrets '\_FILE' Variable |
| :----------------: | :-----------------------------------------: |
| `DB_HOSTNAME` | `DB_HOSTNAME_FILE`<sup>\*1</sup> |
| `DB_DATABASE_NAME` | `DB_DATABASE_NAME_FILE`<sup>\*1</sup> |
| `DB_USERNAME` | `DB_USERNAME_FILE`<sup>\*1</sup> |
| `DB_PASSWORD` | `DB_PASSWORD_FILE`<sup>\*1</sup> |
| `REDIS_PASSWORD` | `REDIS_PASSWORD_FILE`<sup>\*2</sup> |
\*1: See the [official documentation](https://github.com/docker-library/docs/tree/master/postgres#docker-secrets) for
details on how to use Docker Secrets in the Postgres image.
\*2: See [this comment](https://github.com/docker-library/redis/issues/46#issuecomment-335326234) for an example of how
to use use a Docker secret for the password in the Redis container.

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 100
sidebar_position: 80
---
import RegisterAdminUser from '../partials/_register-admin.md';

View File

@@ -10,7 +10,7 @@ I really like the Japanese culture, especially the books, history, and food. The
![Oda_emblem](https://user-images.githubusercontent.com/27055614/182044504-a5ed33a8-5640-42de-b359-18fdbee9fb90.svg)
One of my favorite books is [Taikō](https://www.goodreads.com/book/show/336228.Taiko), it is a story about a prominent figure in the history of Japan, [Toyotomy Hideyoshi](https://www.britannica.com/biography/Toyotomi-Hideyoshi). He came from nothing, and through his resilience and wonderful mind, he has become one of the most powerful rulers in Japan's history. I enjoy his personality and the way he moved through life.
One of my favorite books is [Taikō](https://www.goodreads.com/book/show/336228.Taiko), it is a story about a prominent figure in the history of Japan, [Toyotomi Hideyoshi](https://www.britannica.com/biography/Toyotomi-Hideyoshi). He came from nothing, and through his resilience and wonderful mind, he has become one of the most powerful rulers in Japan's history. I enjoy his personality and the way he moved through life.
The color is an adaptation of **_App-Which-Must-Not-Be-Named_**'s color scheme, with an extra color (pink) to complete the flower's fifth petal. The petal layers are the same color scheme as the main layer rotating back and forth to "bring the flower to life."

View File

@@ -1,6 +1,6 @@
Immich allows the admin user to set the pattern of how the files are uploaded to the Immich would look like. Both in the directory and the filename level.
Immich allows the admin user to set the uploaded filename pattern. Both at the directory and filename level.
The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text.
The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text. If the template produces [multiple files with the same filename, they won't be overwritten](https://github.com/immich-app/immich/discussions/3324) as a sequence number is appended to the filename.
```bash title="Default template"
Year/Year-Month-Day/Filename.Extension
@@ -8,4 +8,4 @@ Year/Year-Month-Day/Filename.Extension
<img src={require('./img/storage-template.png').default} width="100%" title="Storage Template Setting" />
Immich also provides a mechanism to migrate between template so that if the template you set now doesn't work in the future, you can always migrate all the existing files to the new template. The mechanism is run as a job in the Job page.
Immich also provides a mechanism to migrate between templates so that if the template you set now doesn't work in the future, you can always migrate all the existing files to the new template. The mechanism is run as a job on the Job page.

1494
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,14 @@
"check": "tsc"
},
"dependencies": {
"@docusaurus/core": "2.1.0",
"@docusaurus/preset-classic": "2.1.0",
"@docusaurus/core": "^2.4.1",
"@docusaurus/preset-classic": "^2.4.1",
"@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13",
"clsx": "^1.2.1",
"docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3",
"postcss": "^8.4.20",
"postcss": "^8.4.25",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -32,10 +32,10 @@
"url": "^0.11.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.1.0",
"@docusaurus/module-type-aliases": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.5",
"prettier": "^2.8.8",
"typescript": "^5.0.0"
"typescript": "^5.1.6"
},
"browserslist": {
"production": [

View File

@@ -32,6 +32,19 @@ function HomepageHeader() {
</div>
<img src="/img/immich-screenshots.png" alt="logo" />
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-4 gap-1">
<div className="h-24">
<a href="https://play.google.com/store/apps/details?id=app.alextran.immich">
<img className="h-24" alt="Get it on Google Play" src="/img/google-play-badge.png" />
</a>
</div>
<div className="h-24">
<a href="https://apps.apple.com/sg/app/immich/id1613945652">
<img className="h-24 sm:p-3.5 p-3" alt="Download on the App Store" src="/img/ios-app-store-badge.svg" />
</a>
</div>
</div>
</section>
</header>
);
@@ -40,7 +53,7 @@ function HomepageHeader() {
export default function Home(): JSX.Element {
return (
<Layout
title={`Home`}
title="Home"
description="immich Self-hosted photo and video backup solution directly from your mobile phone "
noFooter={true}
>

BIN
docs/static/img/google-play-badge.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

46
docs/static/img/ios-app-store-badge.svg vendored Executable file
View File

@@ -0,0 +1,46 @@
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
<g>
<g>
<g>
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
</g>
<g id="_Group_" data-name="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
<path id="_Path_2" data-name="&lt;Path&gt;" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
</g>
</g>
<g>
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
</g>
</g>
</g>
<g id="_Group_4" data-name="&lt;Group&gt;">
<g>
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -12,37 +12,65 @@ download:
files:
- file: mobile/assets/i18n/en-US.json
locale_code: en-US
- file: mobile/assets/i18n/da-DK.json
locale_code: da-DK
- file: mobile/assets/i18n/de-DE.json
locale_code: de-DE
- file: mobile/assets/i18n/fr-FR.json
locale_code: fr-FR
- file: mobile/assets/i18n/da-DK.json
locale_code: da-DK
- file: mobile/assets/i18n/it-IT.json
locale_code: it-IT
- file: mobile/assets/i18n/nl-NL.json
locale_code: nl-NL
- file: mobile/assets/i18n/ko-KR.json
locale_code: ko-KR
- file: mobile/assets/i18n/es-ES.json
locale_code: es-ES
- file: mobile/assets/i18n/fi-FI.json
locale_code: fi-FI
- file: mobile/assets/i18n/vi-VN.json
locale_code: vi-VN
- file: mobile/assets/i18n/fr-FR.json
locale_code: fr-FR
- file: mobile/assets/i18n/ja-JP.json
locale_code: ja-JP
- file: mobile/assets/i18n/pt-BR.json
locale_code: pt-BR
- file: mobile/assets/i18n/pl-PL.json
locale_code: pl-PL
- file: mobile/assets/i18n/sv-SE.json
locale_code: sv-SE
- file: mobile/assets/i18n/sk-SK.json
locale_code: sk-SK
- file: mobile/assets/i18n/zh-CN.json
locale_code: zh-CN
- file: mobile/assets/i18n/ru-RU.json
locale_code: ru-RU
- file: mobile/assets/i18n/fi-FI.json
locale_code: fi-FI
- file: mobile/assets/i18n/pt-BR.json
locale_code: pt-BR
- file: mobile/assets/i18n/cs-CZ.json
locale_code: cs-CZ
- file: mobile/assets/i18n/uk-UA.json
locale_code: uk-UA
- file: mobile/assets/i18n/ru-RU.json
locale_code: ru-RU
- file: mobile/assets/i18n/zh-CN.json
locale_code: zh-CN
- file: mobile/assets/i18n/sk-SK.json
locale_code: sk-SK
- file: mobile/assets/i18n/nl-NL.json
locale_code: nl-NL
- file: mobile/assets/i18n/nb-NO.json
locale_code: nb-NO
- file: mobile/assets/i18n/sv-SE.json
locale_code: sv-SE
- file: mobile/assets/i18n/mn.json
locale_code: mn
- file: mobile/assets/i18n/ko-KR.json
locale_code: ko-KR
- file: mobile/assets/i18n/sr-Latn.json
locale_code: sr-Latn
- file: mobile/assets/i18n/sr-Cyrl.json
locale_code: sr-Cyrl
- file: mobile/assets/i18n/hi-IN.json
locale_code: hi-IN
- file: mobile/assets/i18n/es-PE.json
locale_code: es-PE
- file: mobile/assets/i18n/es-MX.json
locale_code: es-MX
- file: mobile/assets/i18n/sv-FI.json
locale_code: sv-FI
- file: mobile/assets/i18n/ca.json
locale_code: ca
- file: mobile/assets/i18n/hu-HU.json
locale_code: hu-HU
- file: mobile/assets/i18n/lv-LV.json
locale_code: lv-LV
- file: mobile/assets/i18n/zh-Hans.json
locale_code: zh-Hans
- file: mobile/assets/i18n/th-TH.json
locale_code: th-TH

View File

@@ -10,11 +10,14 @@ RUN poetry config installer.max-workers 10 && \
RUN python -m venv /opt/venv
ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
COPY poetry.lock pyproject.toml ./
COPY poetry.lock pyproject.toml requirements.txt ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
RUN pip install --no-deps -r requirements.txt
FROM python:3.11.4-slim-bullseye@sha256:91d194f58f50594cda71dcd2e8fdefd90e7ecc57d07823813b67c8521e565dcd
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
@@ -25,4 +28,5 @@ ENV NODE_ENV=production \
COPY --from=builder /opt/venv /opt/venv
COPY app .
ENTRYPOINT ["python", "-m", "app.main"]
ENTRYPOINT ["tini", "--"]
CMD ["python", "-m", "app.main"]

View File

@@ -0,0 +1,21 @@
# Immich Machine Learning
- Clasificación de imágenes
- Incorporación de CLIP
- Reconocimiento facial
# Configuración
Este proyecto utiliza [Poetry](https://python-poetry.org/docs/#installation), así que asegúrate de instalarlo primero.
Ejecutar `poetry install --no-root --with dev` instalará todo lo necesario en un entorno virtual aislado.
Para agregar o eliminar dependencias, puedes utilizar los comandos `poetry add $PACKAGE_NAME` y `poetry remove $PACKAGE_NAME`, respectivamente.
Asegúrate de hacer commit de los archivos `poetry.lock` y `pyproject.toml` para reflejar cualquier cambio en las dependencias.
# Pruebas de carga
Para medir la velocidad y latencia de inferencia, puedes utilizar [Locust](https://locust.io/) con el archivo `locustfile.py` proporcionado.
Locust funciona haciendo consultas a los puntos finales del modelo y agregando estadísticas, lo que significa que la aplicación debe estar desplegada.
Puedes ejecutar `load_test.sh` para implementar automáticamente la aplicación localmente e iniciar Locust, ajustando opcionalmente sus variables de entorno según sea necesario.
Alternativamente, para pruebas más personalizadas, también puedes ejecutar `locust` directamente: consulta la [documentación](https://docs.locust.io/en/stable/index.html). Ten en cuenta que, en la jerga de Locust, la concurrencia se mide en `usuarios`, y cada usuario ejecuta una tarea a la vez. Para lograr una concurrencia específica por punto final, multiplica ese número por la cantidad de puntos finales que se desean consultar. Por ejemplo, si hay 3 puntos finales y deseas que cada uno de ellos reciba 8 solicitudes al mismo tiempo, debes configurar el número de usuarios en 24.

View File

@@ -1,3 +1,4 @@
import os
from pathlib import Path
from pydantic import BaseSettings
@@ -8,24 +9,31 @@ from .schemas import ModelType
class Settings(BaseSettings):
cache_folder: str = "/cache"
classification_model: str = "microsoft/resnet-50"
clip_image_model: str = "clip-ViT-B-32"
clip_text_model: str = "clip-ViT-B-32"
clip_image_model: str = "ViT-B-32::openai"
clip_text_model: str = "ViT-B-32::openai"
facial_recognition_model: str = "buffalo_l"
min_tag_score: float = 0.9
eager_startup: bool = True
model_ttl: int = 300
eager_startup: bool = False
model_ttl: int = 0
host: str = "0.0.0.0"
port: int = 3003
workers: int = 1
min_face_score: float = 0.7
test_full: bool = False
request_threads: int = os.cpu_count() or 4
model_inter_op_threads: int = 1
model_intra_op_threads: int = 2
class Config(BaseSettings.Config):
class Config:
env_prefix = "MACHINE_LEARNING_"
case_sensitive = False
_clean_name = str.maketrans(":\\/", "___", ".")
def get_cache_dir(model_name: str, model_type: ModelType) -> Path:
return Path(settings.cache_folder, model_type.value, model_name)
return Path(settings.cache_folder) / model_type.value / model_name.translate(_clean_name)
settings = Settings()

View File

@@ -0,0 +1,33 @@
from typing import Iterator, TypeAlias
from unittest import mock
import numpy as np
import pytest
from fastapi.testclient import TestClient
from PIL import Image
from .main import app, init_state
ndarray: TypeAlias = np.ndarray[int, np.dtype[np.float32]]
@pytest.fixture
def pil_image() -> Image.Image:
return Image.new("RGB", (600, 800))
@pytest.fixture
def cv_image(pil_image: Image.Image) -> ndarray:
return np.asarray(pil_image)[:, :, ::-1] # PIL uses RGB while cv2 uses BGR
@pytest.fixture
def mock_get_model() -> Iterator[mock.Mock]:
with mock.patch("app.models.cache.InferenceModel.from_model_type", autospec=True) as mocked:
yield mocked
@pytest.fixture(scope="session")
def deployed_app() -> TestClient:
init_state()
return TestClient(app)

View File

@@ -1,4 +1,6 @@
import asyncio
import os
from concurrent.futures import ThreadPoolExecutor
from io import BytesIO
from typing import Any
@@ -8,8 +10,9 @@ import uvicorn
from fastapi import Body, Depends, FastAPI
from PIL import Image
from app.models.base import InferenceModel
from .config import settings
from .models.base import InferenceModel
from .models.cache import ModelCache
from .schemas import (
EmbeddingResponse,
@@ -24,32 +27,41 @@ from .schemas import (
app = FastAPI()
@app.on_event("startup")
async def startup_event() -> None:
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=True)
same_clip = settings.clip_image_model == settings.clip_text_model
app.state.clip_vision_type = ModelType.CLIP if same_clip else ModelType.CLIP_VISION
app.state.clip_text_type = ModelType.CLIP if same_clip else ModelType.CLIP_TEXT
models = [
(settings.classification_model, ModelType.IMAGE_CLASSIFICATION),
(settings.clip_image_model, app.state.clip_vision_type),
(settings.clip_text_model, app.state.clip_text_type),
(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION),
def init_state() -> None:
app.state.model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
app.state.thread_pool = ThreadPoolExecutor(settings.request_threads)
async def load_models() -> None:
models: list[tuple[str, ModelType, dict[str, Any]]] = [
(settings.classification_model, ModelType.IMAGE_CLASSIFICATION, {}),
(settings.clip_image_model, ModelType.CLIP, {"mode": "vision"}),
(settings.clip_text_model, ModelType.CLIP, {"mode": "text"}),
(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION, {}),
]
# Get all models
for model_name, model_type in models:
if settings.eager_startup:
await app.state.model_cache.get(model_name, model_type)
else:
InferenceModel.from_model_type(model_type, model_name)
for model_name, model_type, model_kwargs in models:
await app.state.model_cache.get(model_name, model_type, eager=settings.eager_startup, **model_kwargs)
@app.on_event("startup")
async def startup_event() -> None:
init_state()
await load_models()
@app.on_event("shutdown")
async def shutdown_event() -> None:
app.state.thread_pool.shutdown()
def dep_pil_image(byte_image: bytes = Body(...)) -> Image.Image:
return Image.open(BytesIO(byte_image))
def dep_cv_image(byte_image: bytes = Body(...)) -> cv2.Mat:
def dep_cv_image(byte_image: bytes = Body(...)) -> np.ndarray[int, np.dtype[Any]]:
byte_image_np = np.frombuffer(byte_image, np.uint8)
return cv2.imdecode(byte_image_np, cv2.IMREAD_COLOR)
@@ -72,10 +84,8 @@ def ping() -> str:
async def image_classification(
image: Image.Image = Depends(dep_pil_image),
) -> list[str]:
model = await app.state.model_cache.get(
settings.classification_model, ModelType.IMAGE_CLASSIFICATION
)
labels = model.predict(image)
model = await app.state.model_cache.get(settings.classification_model, ModelType.IMAGE_CLASSIFICATION)
labels = await predict(model, image)
return labels
@@ -87,10 +97,8 @@ async def image_classification(
async def clip_encode_image(
image: Image.Image = Depends(dep_pil_image),
) -> list[float]:
model = await app.state.model_cache.get(
settings.clip_image_model, app.state.clip_vision_type
)
embedding = model.predict(image)
model = await app.state.model_cache.get(settings.clip_image_model, ModelType.CLIP, mode="vision")
embedding = await predict(model, image)
return embedding
@@ -100,10 +108,8 @@ async def clip_encode_image(
status_code=200,
)
async def clip_encode_text(payload: TextModelRequest) -> list[float]:
model = await app.state.model_cache.get(
settings.clip_text_model, app.state.clip_text_type
)
embedding = model.predict(payload.text)
model = await app.state.model_cache.get(settings.clip_text_model, ModelType.CLIP, mode="text")
embedding = await predict(model, payload.text)
return embedding
@@ -115,13 +121,15 @@ async def clip_encode_text(payload: TextModelRequest) -> list[float]:
async def facial_recognition(
image: cv2.Mat = Depends(dep_cv_image),
) -> list[dict[str, Any]]:
model = await app.state.model_cache.get(
settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION
)
faces = model.predict(image)
model = await app.state.model_cache.get(settings.facial_recognition_model, ModelType.FACIAL_RECOGNITION)
faces = await predict(model, image)
return faces
async def predict(model: InferenceModel, inputs: Any) -> Any:
return await asyncio.get_running_loop().run_in_executor(app.state.thread_pool, model.predict, inputs)
if __name__ == "__main__":
is_dev = os.getenv("NODE_ENV") == "development"
uvicorn.run(

View File

@@ -1,3 +1,3 @@
from .clip import CLIPSTTextEncoder, CLIPSTVisionEncoder
from .clip import CLIPEncoder
from .facial_recognition import FaceRecognizer
from .image_classification import ImageClassifier

View File

@@ -1,10 +1,17 @@
from __future__ import annotations
from abc import abstractmethod, ABC
import os
import pickle
from abc import ABC, abstractmethod
from pathlib import Path
from shutil import rmtree
from typing import Any
from zipfile import BadZipFile
from ..config import get_cache_dir
import onnxruntime as ort
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf # type: ignore
from ..config import get_cache_dir, settings
from ..schemas import ModelType
@@ -14,17 +21,61 @@ class InferenceModel(ABC):
def __init__(
self,
model_name: str,
cache_dir: Path | None = None,
):
cache_dir: Path | str | None = None,
eager: bool = True,
inter_op_num_threads: int = settings.model_inter_op_threads,
intra_op_num_threads: int = settings.model_intra_op_threads,
**model_kwargs: Any,
) -> None:
self.model_name = model_name
self._cache_dir = (
cache_dir
if cache_dir is not None
else get_cache_dir(model_name, self.model_type)
self._loaded = False
self._cache_dir = Path(cache_dir) if cache_dir is not None else get_cache_dir(model_name, self.model_type)
loader = self.load if eager else self.download
self.providers = model_kwargs.pop("providers", ["CPUExecutionProvider"])
# don't pre-allocate more memory than needed
self.provider_options = model_kwargs.pop(
"provider_options", [{"arena_extend_strategy": "kSameAsRequested"}] * len(self.providers)
)
self.sess_options = PicklableSessionOptions()
# avoid thread contention between models
if inter_op_num_threads > 1:
self.sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
self.sess_options.inter_op_num_threads = inter_op_num_threads
self.sess_options.intra_op_num_threads = intra_op_num_threads
try:
loader(**model_kwargs)
except (OSError, InvalidProtobuf, BadZipFile):
self.clear_cache()
loader(**model_kwargs)
def download(self, **model_kwargs: Any) -> None:
if not self.cached:
print(f"Downloading {self.model_type.value.replace('_', ' ')} model. This may take a while...")
self._download(**model_kwargs)
def load(self, **model_kwargs: Any) -> None:
self.download(**model_kwargs)
self._load(**model_kwargs)
self._loaded = True
def predict(self, inputs: Any) -> Any:
if not self._loaded:
print(f"Loading {self.model_type.value.replace('_', ' ')} model...")
self.load()
return self._predict(inputs)
@abstractmethod
def predict(self, inputs: Any) -> Any:
def _predict(self, inputs: Any) -> Any:
...
@abstractmethod
def _download(self, **model_kwargs: Any) -> None:
...
@abstractmethod
def _load(self, **model_kwargs: Any) -> None:
...
@property
@@ -36,17 +87,40 @@ class InferenceModel(ABC):
return self._cache_dir
@cache_dir.setter
def cache_dir(self, cache_dir: Path):
def cache_dir(self, cache_dir: Path) -> None:
self._cache_dir = cache_dir
@property
def cached(self) -> bool:
return self.cache_dir.exists() and any(self.cache_dir.iterdir())
@classmethod
def from_model_type(
cls, model_type: ModelType, model_name, **model_kwargs
) -> InferenceModel:
subclasses = {
subclass._model_type: subclass for subclass in cls.__subclasses__()
}
def from_model_type(cls, model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel:
subclasses = {subclass._model_type: subclass for subclass in cls.__subclasses__()}
if model_type not in subclasses:
raise ValueError(f"Unsupported model type: {model_type}")
return subclasses[model_type](model_name, **model_kwargs)
def clear_cache(self) -> None:
if not self.cache_dir.exists():
return
if not rmtree.avoids_symlink_attacks:
raise RuntimeError("Attempted to clear cache, but rmtree is not safe on this platform.")
if self.cache_dir.is_dir():
rmtree(self.cache_dir)
else:
self.cache_dir.unlink()
self.cache_dir.mkdir(parents=True, exist_ok=True)
# HF deep copies configs, so we need to make session options picklable
class PicklableSessionOptions(ort.SessionOptions):
def __getstate__(self) -> bytes:
return pickle.dumps([(attr, getattr(self, attr)) for attr in dir(self) if not callable(getattr(self, attr))])
def __setstate__(self, state: Any) -> None:
self.__init__() # type: ignore
for attr, val in pickle.loads(state):
setattr(self, attr, val)

View File

@@ -1,4 +1,4 @@
import asyncio
from typing import Any
from aiocache.backends.memory import SimpleMemoryCache
from aiocache.lock import OptimisticLock
@@ -34,13 +34,9 @@ class ModelCache:
if profiling:
plugins.append(TimingPlugin())
self.cache = SimpleMemoryCache(
ttl=ttl, timeout=timeout, plugins=plugins, namespace=None
)
self.cache = SimpleMemoryCache(ttl=ttl, timeout=timeout, plugins=plugins, namespace=None)
async def get(
self, model_name: str, model_type: ModelType, **model_kwargs
) -> InferenceModel:
async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel:
"""
Args:
model_name: Name of model in the model hub used for the task.
@@ -50,16 +46,11 @@ class ModelCache:
model: The requested model.
"""
key = self.cache.build_key(model_name, model_type.value)
model = await self.cache.get(key)
if model is None:
async with OptimisticLock(self.cache, key) as lock:
model = await asyncio.get_running_loop().run_in_executor(
None,
lambda: InferenceModel.from_model_type(
model_type, model_name, **model_kwargs
),
)
key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}"
async with OptimisticLock(self.cache, key) as lock:
model = await self.cache.get(key)
if model is None:
model = InferenceModel.from_model_type(model_type, model_name, **model_kwargs)
await lock.cas(model, ttl=self.ttl)
return model
@@ -73,7 +64,14 @@ class ModelCache:
class RevalidationPlugin(BasePlugin):
"""Revalidates cache item's TTL after cache hit."""
async def post_get(self, client, key, ret=None, namespace=None, **kwargs):
async def post_get(
self,
client: SimpleMemoryCache,
key: str,
ret: Any | None = None,
namespace: str | None = None,
**kwargs: Any,
) -> None:
if ret is None:
return
if namespace is not None:
@@ -81,7 +79,14 @@ class RevalidationPlugin(BasePlugin):
if key in client._handlers:
await client.expire(key, client.ttl)
async def post_multi_get(self, client, keys, ret=None, namespace=None, **kwargs):
async def post_multi_get(
self,
client: SimpleMemoryCache,
keys: list[str],
ret: list[Any] | None = None,
namespace: str | None = None,
**kwargs: Any,
) -> None:
if ret is None:
return

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