Compare commits

...

87 Commits

Author SHA1 Message Date
Alex The Bot
3dddc6b449 Version v1.93.2 2024-01-19 18:29:04 +00:00
Alex
88ac3c2016 fix(web): better invite shared user to album layout (#6511)
* fix(web): better invite to album design

* rounded corner

* use icon

* padding
2024-01-19 12:27:29 -06:00
martin
17eaeb695e feat: smart merge (#6508)
* pr feedback

* fix: tests

* update assets statistics

* pr feedback

* pr feedback

* fix: linter

* pr feedback

* fix: don't limit the smart merge

* pr feedback

* fix: server code

* remove slider

* fix: tests

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-19 17:52:26 +00:00
shenlong
f80f867976 fix(mobile): stack button not in bottom app bar (#6497)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-19 17:40:10 +00:00
martin
d15c443d9b fix(web): user list when sharing an album (#6500)
fix: user list when sharing an album

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-01-19 17:38:44 +00:00
Alex
07b874edda fix(web): revert smart merge (#6504)
* revert smart merge

* fix test

* fix test

* Remove Slider file
2024-01-19 11:34:20 -06:00
Jason Rasmussen
df27460f1c fix: open api pump (#6502) 2024-01-19 11:09:18 -06:00
Alex The Bot
d5af357992 Version v1.93.1 2024-01-19 15:01:17 +00:00
Jason Rasmussen
dacca4cdf1 fix(web): slider (#6485) 2024-01-19 08:57:15 -06:00
Mert
b4d1470586 fix(web): prevent layout change from scrollbar in admin setings (#6482)
stable scrollbar
2024-01-19 03:30:48 +00:00
Alex Tran
20c284407c chore: post release openapi update 2024-01-18 21:21:28 -06:00
Alex Tran
1af5fcfcde chore: post release openapi update 2024-01-18 21:19:39 -06:00
Alex
7e1b1eae41 chore: post release tasks 2024-01-18 21:15:58 -06:00
Alex The Bot
fa0b7c8563 Version v1.93.0 2024-01-19 02:14:46 +00:00
shenlong
f62678f58f feat(mobile): long-press delete button to permanently delete asset (#6240)
* feat(mobile): delete assets from device only

* mobile: add backed up only toggle for delete device only

* remove toggle inside alert and show different content

* mobile: change content color for local only

* mobile: delete local only button to dialog

* style: display bottom action in two lines

* feat: separate delete buttons

* fix: incorrect error message for ownedRemoteSelection

* feat(mobile): long-press delete to permanently delete asset

* chore: add todo to handle long press to delete in gallery_viewer

* chore: rebase on deletion branch

* feat(mobile): long-press delete to permanently delete asset

* fix(mobile): update minChildSize of control bottom app bar

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-01-18 15:01:38 -06:00
shenlong
04c783f2f0 fix(mobile): asset state when delete from trash (#6476)
* fix(mobile): handle asset removal state from trash for merged assets

* fix(mobile): use appropriate text for trash / delete

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-18 14:55:19 -06:00
haosu
660b2e908d feat(format): hif format (#6477) 2024-01-18 11:18:56 -06:00
Jason Rasmussen
91efe7f7ae chore: update bug template (#6465)
* chore: update bug template

* Update .github/ISSUE_TEMPLATE/bug_report.yaml

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

---------

Co-authored-by: bo0tzz <git@bo0tzz.me>
2024-01-18 10:02:13 -06:00
martin
02393126e6 fix(web): trash or delete (#6475)
fix: trash or delete
2024-01-18 10:01:39 -06:00
Mert
68f52818ae feat(server): separate face clustering job (#5598)
* separate facial clustering job

* update api

* fixed some tests

* invert clustering

* hdbscan

* update api

* remove commented code

* wip dbscan

* cleanup

removed cluster endpoint

remove commented code

* fixes

updated tests

minor fixes and formatting

fixed queuing

refinements

* scale search range based on library size

* defer non-core faces

* optimizations

removed unused query option

* assign faces individually for correctness

fixed unit tests

remove unused method

* don't select face embedding

update sql

linting

fixed ml typing

* updated job mock

* paginate people query

* select face embeddings because typeorm

* fix setting face detection concurrency

* update sql

formatting

linting

* simplify logic

remove unused imports

* more specific delete signature

* more accurate typing for face stubs

* add migration

formatting

* chore: better typing

* don't select embedding by default

remove unused import

* updated sql

* use normal try/catch

* stricter concurrency typing and enforcement

* update api

* update job concurrency panel to show disabled queues

formatting

* check jobId in queueAll

fix tests

* remove outdated comment

* better facial recognition icon

* wording

wording

formatting

* fixed tests

* fix

* formatting & sql

* try to fix sql check

* more detailed description

* update sql

* formatting

* wording

* update `minFaces` description

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-01-18 00:08:48 -05:00
Jason Rasmussen
44873b4224 deps: update docs (#6462) 2024-01-17 22:44:45 -05:00
Jason Rasmussen
98cee8864d docs: remove tsc check (#6464) 2024-01-17 22:37:48 -05:00
Mert
9a2fa21b28 fix(server): scale transcoded videos if dimensions are odd (#6461)
scale if odd resolution
2024-01-17 22:16:44 -05:00
Bohan Zhang
b98d1bf9d3 fix(cli): uploadCounters increase only when files are uploaded (#6357)
* uploadcounters increase only when files are uploaded

* move up totalSizeUploaded counter
2024-01-18 03:15:13 +00:00
Steven Carter
d4146e3e6d feat(server): provide the ability to search archived photos (#6332)
* Feat: provide the ability to search archived photos

Adds a query parameter (`searchArchived`) to the search URL parameters
to allow the results to contain archived photos.

* chore: rename includeArchived => withArchived

* chore: open api

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-18 02:08:00 +00:00
martin
f0b328fb6b feat(server, web): smart merge (#5796)
* pr feedback

* fix: tests

* update assets statistics

* pr feedback

* pr feedback

* fix: linter

* pr feedback

* fix: don't limit the smart merge

* pr feedback

* fix: server code

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-18 01:52:11 +00:00
Alex
c55503496f fix(server): set log level of immich-admin process in boostrap function (#6458)
* fix(server): set log level of immich-admin process in boostrap function

* Remove log
2024-01-17 16:47:46 -05:00
Jason Rasmussen
6f291006e4 fix(server): handle 5 digit years (#6457) 2024-01-17 16:08:38 -05:00
waclaw66
574aecc1e2 fix(mobile): add to album - list thumbnails (#6444)
fix(mobile): add to album thumbnails
2024-01-17 14:27:55 -06:00
martin
c317feaf93 feat(web): force delete with shift key (#6239)
* feat: force delete with shift key

* fix: types import

* pr feedback

* fix: permanently delete assets

* fix: format

* fix: remove unused variable

* change info title

* simplify

* fix: rename function name

* pr feedback

* simplify

* pr feedback

* add toggle in the user settings

* fix: trash settings, input label, and wording

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-17 19:18:04 +00:00
renovate[bot]
0350058689 fix(deps): update server (#6415)
* fix(deps): update server

* chore: fix tests

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-17 18:24:51 +00:00
Hiren Shah
a7768cc64d docs: Update remove-offline-files.md (#6449)
The previous Windows Powershell script didn't work - resulted in a bunch of errors due to multiple reasons.  The revised code works as expected.
2024-01-17 11:40:33 -05:00
Alexander Welsing
702e91145a Move 'Add' button on album user invite to the same row as 'To' List (#6447)
Move 'Add' button on album user invite to the same row as 'To' List in order to prevent the button getting hidden by a scrollbar
2024-01-17 14:43:41 +00:00
shenlong
4c2befc68c feat(mobile): separate delete buttons (#4505)
* feat(mobile): delete assets from device only

* mobile: add backed up only toggle for delete device only

* remove toggle inside alert and show different content

* mobile: change content color for local only

* mobile: delete local only button to dialog

* style: display bottom action in two lines

* feat: separate delete buttons

* fix: incorrect error message for ownedRemoteSelection

* fix: handle remoteOnly from delete everywhere

* request confirmation for local only only when non-backed assets are in selection

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-01-16 21:28:23 -06:00
Alex
78de4f1312 feat(mobile): quota (#6409)
* feat(mobile): quota

* openapi

* user entity update

* Render quota

* refresh usage upon opening the app bar

* stop backup when quota exceed
2024-01-16 20:08:31 -06:00
Jason Rasmussen
abce82e235 fix(server): enable/disable password login on truenas (#6433) 2024-01-16 16:40:09 -05:00
renovate[bot]
d3ff2408bc chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to 377aafa (#6434)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-16 16:39:31 -05:00
renovate[bot]
76b66e42e1 chore(deps): update base-image to v20240111 (major) (#6355)
chore(deps): update base-image to v20240111

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-16 11:09:59 -05:00
renovate[bot]
7b0104f905 chore(deps): update dependency @types/node to v20.11.0 (#6424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-16 09:38:20 -05:00
Jason Rasmussen
8e2d790c2a deps: separate out reflect metadata (#6425) 2024-01-16 09:37:28 -05:00
renovate[bot]
9300946ff1 fix(deps): update docs (#6420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-16 09:27:04 -05:00
renovate[bot]
6457436d91 chore(deps): update web (#6413) 2024-01-15 19:51:10 -05:00
renovate[bot]
9976b2ae92 chore(deps): update @immich/cli (#6412)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/node](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://togithub.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`20.10.6` ->
`20.10.8`](https://renovatebot.com/diffs/npm/@types%2fnode/20.10.6/20.10.8)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/20.10.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2fnode/20.10.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2fnode/20.10.6/20.10.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/20.10.6/20.10.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@typescript-eslint/eslint-plugin](https://togithub.com/typescript-eslint/typescript-eslint)
([source](https://togithub.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin))
| [`6.17.0` ->
`6.18.1`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/6.17.0/6.18.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2feslint-plugin/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2feslint-plugin/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2feslint-plugin/6.17.0/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2feslint-plugin/6.17.0/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@typescript-eslint/parser](https://togithub.com/typescript-eslint/typescript-eslint)
([source](https://togithub.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser))
| [`6.17.0` ->
`6.18.1`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fparser/6.17.0/6.18.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fparser/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2fparser/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2fparser/6.17.0/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fparser/6.17.0/6.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [axios](https://axios-http.com)
([source](https://togithub.com/axios/axios)) | [`1.6.4` ->
`1.6.5`](https://renovatebot.com/diffs/npm/axios/1.6.4/1.6.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/axios/1.6.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/axios/1.6.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/axios/1.6.4/1.6.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/axios/1.6.4/1.6.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[eslint-plugin-jest](https://togithub.com/jest-community/eslint-plugin-jest)
| [`27.6.1` ->
`27.6.2`](https://renovatebot.com/diffs/npm/eslint-plugin-jest/27.6.1/27.6.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-jest/27.6.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-jest/27.6.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-jest/27.6.1/27.6.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-jest/27.6.1/27.6.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[eslint-plugin-prettier](https://togithub.com/prettier/eslint-plugin-prettier)
| [`5.1.2` ->
`5.1.3`](https://renovatebot.com/diffs/npm/eslint-plugin-prettier/5.1.2/5.1.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-prettier/5.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/eslint-plugin-prettier/5.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/eslint-plugin-prettier/5.1.2/5.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-prettier/5.1.2/5.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>typescript-eslint/typescript-eslint
(@&#8203;typescript-eslint/eslint-plugin)</summary>

###
[`v6.18.1`](https://togithub.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#6181-2024-01-08)

[Compare
Source](https://togithub.com/typescript-eslint/typescript-eslint/compare/v6.18.0...v6.18.1)

##### 🩹 Fixes

- **eslint-plugin:** \[no-non-null-assertion] provide valid fix when
member access is on next line

- **eslint-plugin:** \[no-unnecessary-condition] improve checking
optional callee

- **eslint-plugin:** \[prefer-readonly] support modifiers of unions and
intersections

- **eslint-plugin:** \[switch-exhaustiveness-check] fix new
allowDefaultCaseForExhaustiveSwitch option

##### ❤️  Thank You

-   auvred
-   James
-   Josh Goldberg 
-   YeonJuan

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

###
[`v6.18.0`](https://togithub.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#6180-2024-01-06)

[Compare
Source](https://togithub.com/typescript-eslint/typescript-eslint/compare/v6.17.0...v6.18.0)

##### 🚀 Features

-   **typescript-estree:** throw on invalid update expressions

- **eslint-plugin:** \[no-var-requires, no-require-imports] allow option

##### ❤️  Thank You

-   auvred
-   Joshua Chen

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

</details>

<details>
<summary>typescript-eslint/typescript-eslint
(@&#8203;typescript-eslint/parser)</summary>

###
[`v6.18.1`](https://togithub.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#6181-2024-01-08)

[Compare
Source](https://togithub.com/typescript-eslint/typescript-eslint/compare/v6.18.0...v6.18.1)

This was a version bump only for parser to align it with other projects,
there were no code changes.

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

###
[`v6.18.0`](https://togithub.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#6180-2024-01-06)

[Compare
Source](https://togithub.com/typescript-eslint/typescript-eslint/compare/v6.17.0...v6.18.0)

This was a version bump only for parser to align it with other projects,
there were no code changes.

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

</details>

<details>
<summary>axios/axios (axios)</summary>

###
[`v1.6.5`](https://togithub.com/axios/axios/blob/HEAD/CHANGELOG.md#165-2024-01-05)

[Compare
Source](https://togithub.com/axios/axios/compare/v1.6.4...v1.6.5)

##### Bug Fixes

- **ci:** refactor notify action as a job of publish action;
([#&#8203;6176](https://togithub.com/axios/axios/issues/6176))
([0736f95](0736f95ce8))
- **dns:** fixed lookup error handling;
([#&#8203;6175](https://togithub.com/axios/axios/issues/6175))
([f4f2b03](f4f2b039dd))

##### Contributors to this release

- <img
src="https://avatars.githubusercontent.com/u/12586868?v&#x3D;4&amp;s&#x3D;18"
alt="avatar" width="18"/> [Dmitriy
Mozgovoy](https://togithub.com/DigitalBrainJS "+41/-6 (#&#8203;6176
#&#8203;6175 )")
- <img
src="https://avatars.githubusercontent.com/u/4814473?v&#x3D;4&amp;s&#x3D;18"
alt="avatar" width="18"/> [Jay](https://togithub.com/jasonsaayman "+6/-1
()")

</details>

<details>
<summary>jest-community/eslint-plugin-jest
(eslint-plugin-jest)</summary>

###
[`v27.6.2`](https://togithub.com/jest-community/eslint-plugin-jest/blob/HEAD/CHANGELOG.md#2762-2024-01-10)

[Compare
Source](https://togithub.com/jest-community/eslint-plugin-jest/compare/v27.6.1...v27.6.2)

##### Reverts

- Revert "chore: use relative path to parent `tsconfig.json`
([#&#8203;1476](https://togithub.com/jest-community/eslint-plugin-jest/issues/1476))"
([5e6199d](5e6199d621)),
closes
[#&#8203;1476](https://togithub.com/jest-community/eslint-plugin-jest/issues/1476)

</details>

<details>
<summary>prettier/eslint-plugin-prettier
(eslint-plugin-prettier)</summary>

###
[`v5.1.3`](https://togithub.com/prettier/eslint-plugin-prettier/blob/HEAD/CHANGELOG.md#513)

[Compare
Source](https://togithub.com/prettier/eslint-plugin-prettier/compare/v5.1.2...v5.1.3)

##### Patch Changes

-
[#&#8203;629](https://togithub.com/prettier/eslint-plugin-prettier/pull/629)
[`985b33c`](985b33c56f)
Thanks [@&#8203;JounQin](https://togithub.com/JounQin)! - chore: add
`package.json` into `exports` map

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "on tuesday" (UTC), Automerge - At any
time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config help](https://togithub.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/immich-app/immich).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xMjcuMCIsInVwZGF0ZWRJblZlciI6IjM3LjEyNy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 19:48:10 -05:00
Alex
76bad762d7 fix(mobile): null check on null value on top app bar (#6406)
Fixed issue with null check on null value causing the top app bar render
a gray overlay when open an asset in Album On Device section in Library
page
2024-01-16 00:25:59 +00:00
Sushain Cherivirala
7fc1954e2a fix(server): add filename search (#6394)
Fixes https://github.com/immich-app/immich/issues/5982.

There are basically three options:

1. Search `originalFileName` by dropping a file extension from the query
(if present). Lower fidelity but very easy - just a standard index &
equality.
2. Search `originalPath` by adding an index on `reverse(originalPath)`
and using `starts_with(reverse(query) + "/", reverse(originalPath)`. A
weird index & query but high fidelity.
3. Add a new generated column called `originalFileNameWithExtension` or
something. More storage, kinda jank.

TBH, I think (1) is good enough and easy to make better in the future.
For example, if I search "DSC_4242.jpg", I don't really think it matters
if "DSC_4242.mov" also shows up.

edit: There's a fourth approach that we discussed a bit in Discord and
decided we could switch to it in the future: using a GIN. The minor
issue is that Postgres doesn't tokenize paths in a useful (they're a
single token and it won't match against partial components). We can
solve that by tokenizing it ourselves. For example:

```
immich=# with vecs as (select to_tsvector('simple', array_to_string(string_to_array('upload/library/sushain/2015/2015-08-09/IMG_275.JPG', '/'), ' ')) as vec)  select * from vecs where vec @@ phraseto_tsquery('simple', array_to_string(string_to_array('library/sushain', '/'), ' '));
                                      vec
-------------------------------------------------------------------------------
 '-08':6 '-09':7 '2015':4,5 'img_275.jpg':8 'library':2 'sushain':3 'upload':1
(1 row)
```

The query is also tokenized with the 'split-by-slash-join-with-space'
strategy. This strategy results in `IMG_275.JPG`, `2015`, `sushain` and
`library/sushain` matching. But, `08` and `IMG_275` do not match. The
former is because the token is `-08` and the latter because the
`img_275.jpg` token is matched against exactly.
2024-01-15 14:40:28 -06:00
renovate[bot]
f160969894 fix(deps): update dependency com.google.guava:guava to v33 (#5390)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.google.guava:guava](https://togithub.com/google/guava) |
`31.0.1-android` -> `33.0.0-android` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/com.google.guava:guava/33.0.0-android?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/com.google.guava:guava/33.0.0-android?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/com.google.guava:guava/31.0.1-android/33.0.0-android?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/com.google.guava:guava/31.0.1-android/33.0.0-android?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>google/guava (com.google.guava:guava)</summary>

### [`v32.1.3`](https://togithub.com/google/guava/releases/tag/v32.1.3):
32.1.3

[Compare
Source](https://togithub.com/google/guava/compare/v32.1.2...v32.1.3)

##### Maven

```xml
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.1.3-jre</version>
  <!-- or, for Android: -->
  <version>32.1.3-android</version>
</dependency>
```

##### Jar files

-
[32.1.3-jre.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.3-jre/guava-32.1.3-jre.jar)
-
[32.1.3-android.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.3-android/guava-32.1.3-android.jar)

Guava requires [one runtime
dependency](https://togithub.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies),
which you can download here:

-
[failureaccess-1.0.1.jar](https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar)

##### Javadoc

-   [32.1.3-jre](http://guava.dev/releases/32.1.3-jre/api/docs/)
-   [32.1.3-android](http://guava.dev/releases/32.1.3-android/api/docs/)

##### JDiff

- [32.1.3-jre vs.
32.1.2-jre](http://guava.dev/releases/32.1.3-jre/api/diffs/)
- [32.1.3-android vs.
32.1.2-android](http://guava.dev/releases/32.1.3-android/api/diffs/)
- [32.1.3-android vs.
32.1.3-jre](http://guava.dev/releases/32.1.3-android/api/androiddiffs/)

##### Changelog

- Changed Gradle Metadata to include dependency versions directly. This
may address ["Could not find `some-dependency`"
errors](https://togithub.com/google/guava/issues/6657) that some users
have reported (which might be a result of users' excluding
`guava-parent`).
([`c6d35cf`](https://togithub.com/google/guava/commit/c6d35cf1a5))
- `collect`: Changed
`Multisets.unmodifiableMultiset(set).removeIf(predicate)` to throw an
exception always, even if nothing matches `predicate`.
([`61dbccf`](https://togithub.com/google/guava/commit/61dbccfda3))
- `graph`: Fixed the behavior of `Graph`/`ValueGraph` views for a node
when that node is removed from the graph.
([`9507996`](https://togithub.com/google/guava/commit/950799691c))
- `io`: Fixed `Files.createTempDir` and `FileBackedOutputStream` under
[Windows *services*, a rare use
case](https://togithub.com/google/guava/issues/6634). (The fix actually
covers only Java 9+ because Java 8 would require an additional approach.
Let us know if you need support under Java 8.)
([`f87f68c`](https://togithub.com/google/guava/commit/f87f68cd3e))
- `net`: Made `MediaType.parse` allow and skip over whitespace around
the `/` and `=` separator tokens in addition to the `;` separator, for
which it was already being allowed.
([`2786f83`](https://togithub.com/google/guava/commit/2786f83291))
- `util.concurrent`: Tweaked `Futures.getChecked` constructor-selection
behavior: The method continues to prefer to call constructors with a
`String` parameter, but now it breaks ties based on whether the
constructor has a `Throwable` parameter. Beyond that, the choice of
constructor remains undefined. (For this and other reasons, we
discourage the use of `getChecked`.)
([`59cfb22`](https://togithub.com/google/guava/commit/59cfb2267a))

### [`v32.1.2`](https://togithub.com/google/guava/releases/tag/v32.1.2):
32.1.2

[Compare
Source](https://togithub.com/google/guava/compare/v32.1.1...v32.1.2)

##### Maven

```xml
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.1.2-jre</version>
  <!-- or, for Android: -->
  <version>32.1.2-android</version>
</dependency>
```

##### Jar files

-
[32.1.2-jre.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.2-jre/guava-32.1.2-jre.jar)
-
[32.1.2-android.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.2-android/guava-32.1.2-android.jar)

Guava requires [one runtime
dependency](https://togithub.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies),
which you can download here:

-
[failureaccess-1.0.1.jar](https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar)

##### Javadoc

-   [32.1.2-jre](http://guava.dev/releases/32.1.2-jre/api/docs/)
-   [32.1.2-android](http://guava.dev/releases/32.1.2-android/api/docs/)

##### JDiff

- [32.1.2-jre vs.
32.1.1-jre](http://guava.dev/releases/32.1.2-jre/api/diffs/)
- [32.1.2-android vs.
32.1.1-android](http://guava.dev/releases/32.1.2-android/api/diffs/)
- [32.1.2-android vs.
32.1.2-jre](http://guava.dev/releases/32.1.2-android/api/androiddiffs/)

##### Changelog

-
[Removed](https://togithub.com/google/guava/issues/6642#issuecomment-1656201382)
the section of our Gradle metadata that caused Gradle to report
conflicts with `listenablefuture`.
([`9ed0fa6`](https://togithub.com/google/guava/commit/9ed0fa65ab))
- Changed our Maven project to avoid [affecting which version of Mockito
our Gradle users see](https://togithub.com/google/guava/issues/6654).
([`71a16d5`](https://togithub.com/google/guava/commit/71a16d5a74))
- `collect`: Under J2CL, exposed `ImmutableList` and `ImmutableSet`
methods `copyOf` and `of` for JavaScript usage.
([`b41968f`](https://togithub.com/google/guava/commit/b41968f5f2))
- `net`: Optimized `InternetDomainName` construction.
([`3a1d18f`](3a1d18fbef),
[`eaa62eb`](eaa62eb095))

### [`v32.1.1`](https://togithub.com/google/guava/releases/tag/v32.1.1):
32.1.1

[Compare
Source](https://togithub.com/google/guava/compare/v32.1.0...v32.1.1)

##### Maven

```xml
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.1.1-jre</version>
  <!-- or, for Android: -->
  <version>32.1.1-android</version>
</dependency>
```

##### Jar files

-
[32.1.1-jre.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.1-jre/guava-32.1.1-jre.jar)
-
[32.1.1-android.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.1-android/guava-32.1.1-android.jar)

Guava requires [one runtime
dependency](https://togithub.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies),
which you can download here:

-
[failureaccess-1.0.1.jar](https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar)

##### Javadoc

-   [32.1.1-jre](http://guava.dev/releases/32.1.1-jre/api/docs/)
-   [32.1.1-android](http://guava.dev/releases/32.1.1-android/api/docs/)

##### JDiff

- [32.1.1-jre vs.
32.1.0-jre](http://guava.dev/releases/32.1.1-jre/api/diffs/)
- [32.1.1-android vs.
32.1.0-android](http://guava.dev/releases/32.1.1-android/api/diffs/)
- [32.1.1-android vs.
32.1.1-jre](http://guava.dev/releases/32.1.1-android/api/androiddiffs/)

##### Changelog

- Fixed our broken Gradle metadata from
[32.1.0](https://togithub.com/google/guava/releases/tag/v32.1.0). Sorry
again for the trouble. If you use Gradle, please still read [the release
notes from that
version](https://togithub.com/google/guava/releases/tag/v32.1.0): You
may still see errors from the new checking that the metadata enables,
and the release notes discuss how to fix those errors.

### [`v32.1.0`](https://togithub.com/google/guava/releases/tag/v32.1.0):
32.1.0

[Compare
Source](https://togithub.com/google/guava/compare/v32.0.1...v32.1.0)

##### Warning: Our Gradle-metadata version numbers are broken. Read
these notes, but upgrade straight to
[32.1.2](https://togithub.com/google/guava/releases/tag/v32.1.2).

We made a mistake in our release script, so the new Gradle metadata
(discussed below) has [broken version
numbers](https://togithub.com/google/guava/issues/6612) in 32.1.0. Sorry
for the trouble and for the need for another quick patch release. We
recommend upgrading straight to release
[32.1.2](https://togithub.com/google/guava/releases/tag/v32.1.2),
especially if you use Gradle or if you publish a library whose users
might use Gradle. Still, read the release notes below if you use Gradle,
since the fixed Gradle metadata in 32.1.2 may still require action on
your part.

##### Maven

```xml
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.1.0-jre</version>
  <!-- or, for Android: -->
  <version>32.1.0-android</version>
</dependency>
```

##### Jar files

-
[32.1.0-jre.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.0-jre/guava-32.1.0-jre.jar)
-
[32.1.0-android.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.1.0-android/guava-32.1.0-android.jar)

Guava requires [one runtime
dependency](https://togithub.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies),
which you can download here:

-
[failureaccess-1.0.1.jar](https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar)

##### Javadoc

-   [32.1.0-jre](http://guava.dev/releases/32.1.0-jre/api/docs/)
-   [32.1.0-android](http://guava.dev/releases/32.1.0-android/api/docs/)

##### JDiff

- [32.1.0-jre vs.
32.0.1-jre](http://guava.dev/releases/32.1.0-jre/api/diffs/)
- [32.1.0-android vs.
32.0.1-android](http://guava.dev/releases/32.1.0-android/api/diffs/)
- [32.1.0-android vs.
32.1.0-jre](http://guava.dev/releases/32.1.0-android/api/androiddiffs/)

##### Changelog

##### [Gradle Module
Metadata](https://docs.gradle.org/current/userguide/publishing_gradle_module_metadata.html)

**Warning:** We made a mistake in our release script, so this is
[broken](https://togithub.com/google/guava/issues/6612) in 32.1.0. We
recommend upgrading straight to release
[32.1.2](https://togithub.com/google/guava/releases/tag/v32.1.2),
especially if you use Gradle or if you publish a library whose users
might use Gradle. Still, read the release notes below if you use Gradle,
since the fixed Gradle metadata in 32.1.2 may still require action on
your part.

The Gradle team has contributed a metadata file for Guava. If you use
Gradle 6 or higher, you will see better handling of two kinds of
dependency conflicts, plus another small feature related to our
dependencies. As a result of this change, you may see errors, which you
can resolve as documented below. If you encounter a problem that isn't
documented below, or if the documentation is unclear, please [let us
know](https://togithub.com/google/guava/issues/new).

##### If you use Gradle 6 (not 5, not 7+)<a name="gradle-6"></a>

You may see [an error like this
one](https://togithub.com/google/guava/issues/6612#issuecomment-1614897285):

    > Could not resolve all artifacts for configuration ':classpath'.
       > Could not resolve com.google.guava:guava:30.1-jre.
         Required by:
project : >
com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin:2.8.0
> gradle.plugin.com.google.cloud.tools:jib-gradle-plugin:2.8.0
> The consumer was configured to find a runtime of a library compatible
with Java 15, packaged as a jar, and its dependencies declared
externally. However we cannot choose between the following variants of
com.google.guava:guava:32.1.1-jre:
              - androidRuntimeElements
              - jreRuntimeElements
            All of them match the consumer attributes:
- Variant 'androidRuntimeElements' capabilities
com.google.collections:google-collections:32.1.1-jre and
com.google.guava:guava:32.1.1-jre and
com.google.guava:listenablefuture:1.0 declares a runtime of a library
compatible with Java 8, packaged as a jar, and its dependencies declared
externally:

If you do, you'll need to add [something like
this](https://togithub.com/google/guava/issues/6612#issuecomment-1614992368)
to a place where you configure the Java plugins:

```kotlin
sourceSets.all {
  configurations.getByName(runtimeClasspathConfigurationName) {
    attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm")
  }
  configurations.getByName(compileClasspathConfigurationName) {
    attributes.attribute(Attribute.of("org.gradle.jvm.environment", String), "standard-jvm")
  }
}
```

##### If you see an error about a duplicate `ListenableFuture` class<a
name="duplicate-ListenableFuture"></a>

For example:

    Execution failed for task ':app:checkDebugDuplicateClasses'.
> A failure occurred while executing
com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
> Duplicate class com.google.common.util.concurrent.ListenableFuture
found in modules jetified-guava-32.1.1-android
(com.google.guava:guava:32.1.1-android) and
jetified-listenablefuture-1.0 (com.google.guava:listenablefuture:1.0)

This [appears to be a Gradle
bug](https://togithub.com/gradle/gradle/issues/22326#issuecomment-1617422240).

[@&#8203;mathisdt](https://togithub.com/mathisdt) has provided [a
workaround](https://togithub.com/google/guava/issues/6618):

    dependencies {
    ### dependency definitions here ...
      modules {
        module("com.google.guava:listenablefuture") {
replacedBy("com.google.guava:guava", "listenablefuture is part of
guava")
        }
      }
    }

##### Selecting the appropriate flavor<a name="selecting-flavor"></a>

When Gradle automatically selects the newest version of Guava in your
dependency graph, it will now also select the appropriate flavor
(`-android` or `-jre`) based on whether you project targets Android or
not. For example, if you depend on 32.1.0-android and 30.0-jre, Gradle
will select 32.1.0-jre. This is the version most likely to be compatible
with all your dependencies.

In the unusual event that you need to override Gradle's choice of
flavor, you can do so as follows:

```kotlin
dependencies.constraints {
  implementation("com.google.guava:guava") {
    attributes {
      attribute(
        TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, 
        objects.named(TargetJvmEnvironment, TargetJvmEnvironment.ANDROID))
    }
  }
}

// If the above leads to a conflict error because there are additional transitive dependencies to Guava, then use:
configurations.all {
  resolutionStrategy.capabilitiesResolution.withCapability("com.google.guava:guava") {
    select(candidates.find { it.variantName.contains("android") })
  }
}
```

##### Reporting dependencies that overlap with Guava<a
name="overlap"></a>

If your dependency graph contains the very old `google-collections` or
the [hacky](https://groups.google.com/g/guava-announce/c/Km82fZG68Sw)
`listenablefuture`, Gradle will now report that those libraries contain
duplicates of Guava classes. When this happens, you'll need to tell
Gradle to
[select](https://docs.gradle.org/current/userguide/dependency_capability_conflict.html#sub:selecting-between-candidates)
Guava:

```kotlin
configurations.all {
  resolutionStrategy.capabilitiesResolution.withCapability("com.google.collections:google-collections") {
    select("com.google.guava:guava:0")
  }
  // and/or
  resolutionStrategy.capabilitiesResolution.withCapability("com.google.guava:listenablefuture") {
    select("com.google.guava:guava:0")
  }
}
```

If that doesn't work, please let us know. And let us know whether [our
`replacedBy` workaround](#user-content-duplicate-ListenableFuture) or
[these other
workarounds](https://togithub.com/googleapis/sdk-platform-java/pull/1832#issuecomment-1624315236)
work instead.

##### Omitting annotations at runtime

One dependency of Guava that is not needed at runtime
(`j2objc-annotations`) is now omitted from the runtime classpath. (We
may omit others in the future. See
[#&#8203;6606](https://togithub.com/google/guava/issues/6606).)

##### Other changes

- `collect`: Tweaked more nullness annotations.
([`501a016`](501a01631f),
[`5c23590`](5c2359087a))
- `hash`: Enhanced `crc32c()` to use Java's hardware-accelerated
implementation where available.
([`65c7f10`](https://togithub.com/google/guava/commit/65c7f10ff0))
- `util.concurrent`: Added `Duration`-based `default` methods to
`ListeningExecutorService`.
([`e7714b0`](https://togithub.com/google/guava/commit/e7714b0b8b))
- Began updating [Javadoc](https://guava.dev/api) to focus less on APIs
that have been superseded by additions to the JDK. We're also looking to
add more documentation that directs users to JDK equivalents for our
APIs. Further PRs welcome!
([`c9efc47`](c9efc47995),
[`01dcc2e`](01dcc2e610))
- Fixed some problems with [using Guava from a Java
Agent](https://togithub.com/google/guava/issues/6566). (But we don't
test that configuration, and we don't know how well we'll be able to
keep it working.)
([`e42d4e8`](https://togithub.com/google/guava/commit/e42d4e863b),
[`de62703`](https://togithub.com/google/guava/commit/de62703987))
- Fixed `BootstrapMethodError` when [using `CacheBuilder` from a custom
system class loader](https://togithub.com/google/guava/issues/6565). (As
with the previous item, we're not sure how well we'll be able to keep
this use case working.)
([`a667c38`](https://togithub.com/google/guava/commit/a667c38772))
- Suppressed [a harmless `unusable-by-js`
warning](https://togithub.com/google/guava/issues/6602) seen by users of
`guava-gwt`.

### [`v32.0.1`](https://togithub.com/google/guava/releases/tag/v32.0.1):
32.0.1

[Compare
Source](https://togithub.com/google/guava/compare/v32.0.0...v32.0.1)

##### Maven

```xml
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.0.1-jre</version>
  <!-- or, for Android: -->
  <version>32.0.1-android</version>
</dependency>
```

##### Jar files

-
[32.0.1-jre.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.0.1-jre/guava-32.0.1-jre.jar)
-
[32.0.1-android.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.0.1-android/guava-32.0.1-android.jar)

Guava requires [one runtime
dependency](https://togithub.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies),
which you can download here:

-
[failureaccess-1.0.1.jar](https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar)

##### Javadoc

-   [32.0.1-jre](http://guava.dev/releases/32.0.1-jre/api/docs/)
-   [32.0.1-android](http://guava.dev/releases/32.0.1-android/api/docs/)

##### JDiff

- [32.0.1-jre vs.
32.0.0-jre](http://guava.dev/releases/32.0.1-jre/api/diffs/)
- [32.0.1-android vs.
32.0.0-android](http://guava.dev/releases/32.0.1-android/api/diffs/)
- [32.0.1-android vs.
32.0.1-jre](http://guava.dev/releases/32.0.1-android/api/androiddiffs/)

##### Changelog

- `io`: Fixed `Files.createTempDir` and `FileBackedOutputStream` under
Windows, which broke as part of the security fix in release 32.0.0.
Sorry for the trouble.
([`fdbf77d`](https://togithub.com/google/guava/commit/fdbf77d3f2))

### [`v32.0.0`](https://togithub.com/google/guava/releases/tag/v32.0.0):
32.0.0

[Compare
Source](https://togithub.com/google/guava/compare/v31.0.1...v32.0.0)

##### Maven

```xml
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>32.0.0-jre</version>
  <!-- or, for Android: -->
  <version>32.0.0-android</version>
</dependency>
```

##### Jar files

-
[32.0.0-jre.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.0.0-jre/guava-32.0.0-jre.jar)
-
[32.0.0-android.jar](https://repo1.maven.org/maven2/com/google/guava/guava/32.0.0-android/guava-32.0.0-android.jar)

Guava requires [one runtime
dependency](https://togithub.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies),
which you can download here:

-
[failureaccess-1.0.1.jar](https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar)

##### Javadoc

-   [32.0.0-jre](http://guava.dev/releases/32.0.0-jre/api/docs/)
-   [32.0.0-android](http://guava.dev/releases/32.0.0-android/api/docs/)

##### JDiff

- [32.0.0-jre vs.
31.1-jre](http://guava.dev/releases/32.0.0-jre/api/diffs/)
- [32.0.0-android vs.
31.1-android](http://guava.dev/releases/32.0.0-android/api/diffs/)
- [32.0.0-android vs.
32.0.0-jre](http://guava.dev/releases/32.0.0-android/api/androiddiffs/)

##### Changelog

##### Security fixes

- Reimplemented `Files.createTempDir` and `FileBackedOutputStream` to
further address CVE-2020-8908
([#&#8203;4011](https://togithub.com/google/guava/issues/4011)) and
CVE-2023-2976
([#&#8203;2575](https://togithub.com/google/guava/issues/2575)).
([`feb83a1`](https://togithub.com/google/guava/commit/feb83a1c8f))

While CVE-2020-8908 was officially closed when we deprecated
`Files.createTempDir` in [Guava
30.0](https://togithub.com/google/guava/releases/tag/v30.0), we've heard
from users that even recent versions of Guava have been listed as
vulnerable in *other* databases of security vulnerabilities. In
response, we've reimplemented the method (and the very rarely used
`FileBackedOutputStream` class, which had a similar issue) to eliminate
the insecure behavior entirely. This change could technically affect
users in a number of different ways (discussed under "Incompatible
changes" below), but in practice, the only problem users are likely to
encounter is with Windows. If you are using those APIs under Windows,
you should skip 32.0.0 and go straight to
[32.0.1](https://togithub.com/google/guava/releases/tag/v32.0.1) which
fixes the problem. (Unfortunately, we didn't think of the Windows
problem until after the release. And while we [warn that `common.io` in
particular may not work under
Windows](https://togithub.com/google/guava#important-warnings), we
didn't intend to regress support.) Sorry for the trouble.

##### Incompatible changes

Although this release bumps Guava's major version number, it makes **no
binary-incompatible changes to the `guava` artifact**.

One change could cause issues for Widows users, and a few other changes
could cause issues for users in more usual situations:

- **The new implementations of `Files.createTempDir` and
`FileBackedOutputStream` [throw an exception under
Windows](https://togithub.com/google/guava/issues/6535).** This is fixed
in [32.0.1](https://togithub.com/google/guava/releases/tag/v32.0.1).
Sorry for the trouble.
- `guava-gwt` now
[requires](https://togithub.com/google/guava/issues/6627) GWT
[2.10.0](https://togithub.com/gwtproject/gwt/releases/tag/2.10.0).
- This release makes a binary-incompatible change to a `@Beta` API in
the **separate artifact** `guava-testlib`. Specifically, we changed the
return type of `TestingExecutors.sameThreadScheduledExecutor` to
`ListeningScheduledExecutorService`. The old return type was a
package-private class, which caused the Kotlin compiler to produce
warnings.
([`dafaa3e`](https://togithub.com/google/guava/commit/dafaa3e435))
- This release *adds* two methods to the Android flavor of Guava:
`Invokable.getAnnotatedReturnType()` and `Parameter.getAnnotatedType()`.
Those methods do not work under an Android VM; we added them only to
help our tests of the Android flavor (since we also run those tests
under a JRE). Android VMs tolerate such methods as long as the app does
not call them or perform reflection on them, and builds tolerate them
because of our new Proguard configurations (discussed below). Thus, we
expect no impact to most users. However, we could imagine build problems
for users who have set up their own build system for the Android flavor
of Guava. Please report any problems so that we can judge how safely we
might be able to add other methods to the Android flavor in the future,
such as APIs that use Java 8 classes like `Stream`.
([`b30e73c`](b30e73cfa8))
- This release removes various APIs from the `guava-gwt`. This affects
only users of [GWT](https://www.gwtproject.org/). The APIs we removed
are `Enums`, `Sets.complementOf`, and the `Enum*BiMap` classes'
`keyType()` and `valueType()` methods. These changes prepare for the
removal of reflective enum-related APIs from
[J2CL](https://togithub.com/google/j2cl). If one of these changes causes
you problems as a GWT user, let us know.
([`c3a155d`](https://togithub.com/google/guava/commit/c3a155dc85),
[`09db2c2`](https://togithub.com/google/guava/commit/09db2c29ae),
[`3de12be`](https://togithub.com/google/guava/commit/3de12be516))
- The new implementations of `Files.createTempDir` and
`FileBackedOutputStream` are annotated as `@J2ObjCIncompatible`. If you
need to use them under J2ObjC, contact us.
([`56dc928`](https://togithub.com/google/guava/commit/56dc928a25))
- Because the new version of `Files.createTempDir` restricts permissions
to the current user, it could break any caller that relies on letting
other users access the directory.
- The new versions of `Files.createTempDir` and `FileBackedOutputStream`
throw an exception if they can't create the directory or file securely.
Aside from the accidental Windows bug discussed above, this is possible
only under Android Ice Cream Sandwich, a [very
old](https://en.wikipedia.org/wiki/Android_Ice_Cream_Sandwich) version
of Android that is [the oldest one we test Guava
with](https://togithub.com/google/guava#important-warnings).

##### Other changes

- Removed `@Beta` from almost all APIs. For details, see the bottom of
the release notes. At this point, it's probably simpler to look at a
list of APIs that still *are* `@Beta`, such as [this list for
`guava-jre`](https://guava.dev/releases/32.0.0-jre/api/docs/com/google/common/annotations/class-use/Beta.html).
Most of the remaining `@Beta` APIs are in `graph` and `hash`.
- Enhanced the Guava jar to include Proguard configurations that are
picked up automatically by the Android Gradle Plugin. This should help
with warnings that were promoted to errors in Android Gradle Plugin 8.x.
([`aeba1e1`](https://togithub.com/google/guava/commit/aeba1e1b2d))
- Enhanced the Guava jar to include information about method parameters
in its class files. If you use static analyzers that look at
method-parameter names, you may see new warnings or errors if they are
now able to detect mismatches. But mostly, you may see better tooltips
and autocompletion in IDEs.
([`59d174c`](https://togithub.com/google/guava/commit/59d174cfbe))
- Improved nullness annotations on [a few
classes](https://togithub.com/google/guava/issues/6510).
- Modified classes with "serial proxies" to declare exception-throwing
`readObject` methods, in accordance with best practice.
([`e62d6a0`](https://togithub.com/google/guava/commit/e62d6a0456))
- `collect`: Fixed `Maps.newHashMapWithExpectedSize` to stop allocating
maps that were larger than they needed to be.
([`6ad621e`](https://togithub.com/google/guava/commit/6ad621e76d))
- `collect`: Made various APIs work J2CL:
`Maps.immutableEnumMap`+`toImmutableEnumMap`, `EnumMultiset`,
`CollectorTester`. Previously, the APIs were present but failed at
runtime.
([`b62c88e`](https://togithub.com/google/guava/commit/b62c88e630),
[`23ff918`](https://togithub.com/google/guava/commit/23ff91848f),
[`852a7d3`](https://togithub.com/google/guava/commit/852a7d3fe9))
- `collect`: Optimized memory usage for `Interner` and `MapMaker`.
([`a2e8f3c`](https://togithub.com/google/guava/commit/a2e8f3c7ce))
- `graph`: Changed directed graphs to [reject attempts to add undirected
edges](https://togithub.com/google/guava/issues/5843#issuecomment-1136678073).
([`76260d9`](https://togithub.com/google/guava/commit/76260d9b3c))
- `io`: Added `BaseEncoding.ignoreCase()` to support case-insensitive
decoding.
([`9c1e5de`](https://togithub.com/google/guava/commit/9c1e5dea4b))
-   `net`: Added `HttpHeaders` constants:
- `No-Vary-Search`
([`688b9c2`](https://togithub.com/google/guava/commit/688b9c2cfa))
- `Sec-CH-DPR`
([`75a3d4d`](https://togithub.com/google/guava/commit/75a3d4dd36))
- `Sec-CH-UA-Wow64`
([`49e6b9c`](https://togithub.com/google/guava/commit/49e6b9c4a1))
- `Sec-CH-Viewport-Width` and `Sec-CH-Viewport-Height`
([`44df85a`](https://togithub.com/google/guava/commit/44df85a829))
- `Supports-Loading-Mode`
([`0d5c16f`](https://togithub.com/google/guava/commit/0d5c16fc6b))
- `net`: Added the `MediaType` constant for JWT.
([`f942fd2`](https://togithub.com/google/guava/commit/f942fd2c0e))
- `primitives`: Added `rotate()` for arrays of all primitive types.
([`cd338fa`](https://togithub.com/google/guava/commit/cd338fa2bc),
[`6e9057d`](https://togithub.com/google/guava/commit/6e9057d0f2))
- `util.concurrent`: Changed `AbstractFuture` to run `interruptTask()`
just before `afterDone()`. Until this change, it ran slightly earlier
than that: We used to run it before unblocking any pending `get()`
calls, and now we run it after.
([`b337be6`](https://togithub.com/google/guava/commit/b337be6089))
- `util.concurrent`: Fixed some cases in which we could catch
`InterruptedException` but fail to restore the interrupt bit.
([`8f0350a`](https://togithub.com/google/guava/commit/8f0350a21a))

##### `@Beta` removal list

- `base`: `Utf8`
([`211907c`](https://togithub.com/google/guava/commit/211907cb8b))
- `base`: more APIs
([`b0cc461`](https://togithub.com/google/guava/commit/b0cc461da5))
- `collect`: `Multimaps.asMap(...)`
([`df0081f`](https://togithub.com/google/guava/commit/df0081f28f))
- `collect`: `FluentIterable` APIs
([`73b2f7b`](https://togithub.com/google/guava/commit/73b2f7bee0))
- `collect`: `Forwarding[Foo]` APIs
([`9760dbc`](https://togithub.com/google/guava/commit/9760dbcd4c))
- `collect`: `ImmutableFoo.Builder.builderWithExpectedSize(...)`,
`orderEntriesByValue(...)`, and `Entry`-related APIs
([`61be35c`](https://togithub.com/google/guava/commit/61be35ce49))
- `collect`: `RangeMap`, `RangeSet`, and friends
([`fe12c81`](https://togithub.com/google/guava/commit/fe12c81e79))
- `collect`: more APIs
([`98820c7`](https://togithub.com/google/guava/commit/98820c77f9),
[`e5e0f66`](https://togithub.com/google/guava/commit/e5e0f660cc))
- `io`: `ByteStreams`
([`4897930`](https://togithub.com/google/guava/commit/48979309a5))
- `io`: more APIs
([`a589256`](https://togithub.com/google/guava/commit/a5892560de))
- `math`: various APIs
([`912815e`](https://togithub.com/google/guava/commit/912815e4e2))
- `primitives`: `Longs.tryParse(...)` and friends
([`b3d4856`](https://togithub.com/google/guava/commit/b3d48564c6))
- `primitives`: `UnsignedLongs`
([`b240e8c`](https://togithub.com/google/guava/commit/b240e8ce14))
- `primitives`: more APIs
([`fcec25f`](https://togithub.com/google/guava/commit/fcec25f45e),
[`ab4302a`](https://togithub.com/google/guava/commit/ab4302aa53))
- `reflect`: `Invokable` and some methods in `TypeToken`
([`a195f7d`](https://togithub.com/google/guava/commit/a195f7d604))
- `reflect`: `Parameter` (except `getAnnotatedType()` in
`guava-android`)
([`b561eb1`](https://togithub.com/google/guava/commit/b561eb14c2))
- `testing`: various APIs
([`b331769`](https://togithub.com/google/guava/commit/b331769af3),
[`74ad9b8`](https://togithub.com/google/guava/commit/74ad9b8a1f))
- `util.concurrent`: `FluentFuture`
([`b9a2d58`](https://togithub.com/google/guava/commit/b9a2d58503))
- `util.concurrent`: `Futures`
([`15a0c9f`](https://togithub.com/google/guava/commit/15a0c9fd57))
- `util.concurrent`: `Striped`
([`ba8ad69`](https://togithub.com/google/guava/commit/ba8ad69d03))
- `util.concurrent`: various `MoreExecutors` APIs
([`a3571b4`](https://togithub.com/google/guava/commit/a3571b408f))
- `util.concurrent`: more APIs
([`bbaf76a`](https://togithub.com/google/guava/commit/bbaf76a199))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/immich-app/immich).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy41OS44IiwidXBkYXRlZEluVmVyIjoiMzcuMTI3LjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIn0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 18:29:18 +00:00
Jason Rasmussen
3e793c582e docs: fix pgadmin links (#6403) 2024-01-15 17:19:41 +00:00
Jason Rasmussen
fff3a52e60 deps: fix guava versioning (#6402) 2024-01-15 17:03:38 +00:00
shenlong
ba5cca9348 chore(dep): update auto_route (#6390)
* chore(dep): update auto_route

* chore: rebase main

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-15 16:50:33 +00:00
Tom Vincent
984feafb90 fix(server): extract image description (#6344) 2024-01-15 11:19:41 -05:00
shenlong
4f021a74ed chore(renovate): enforce compatible flavor for guava using versionScheme (#6398)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-15 11:15:44 -05:00
shenlong
e6c0f0e3aa refactor(mobile): maplibre (#6087)
* chore: maplibre gl pubspec

* refactor(wip): maplibre for maps

* refactor(wip): dual pane + location button

* chore: remove flutter_map and deps

* refactor(wip): map zoom to location

* refactor: location picker

* open gallery_viewer on marker tap

* remove detectScaleGesture param

* test: debounce and throttle

* chore: rename get location method

* feat(mobile): Adds gps locator to map prompt for easy geolocation (#6282)

* Refactored get gps coords

* Use var for linter's sake, should handle errors better

* Cleanup

* Fix linter issues

* chore(dep): update maplibre to official lib

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Joshua Herrera <joshua.herrera227@gmail.com>
2024-01-15 09:26:13 -06:00
Justin van der Krieken
aa8c54e248 feat(mobile): Focus search on doubletap nav button (#6048)
* feat(mobile): Focus search on doubletap nav button

* Update mobile/lib/modules/search/ui/immich_search_bar.dart

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>

* Move search notifier inside search bar file

And fix naming to better represent type.

* Remove onSearchFocusRequest and call focusSearch directly

* Fix compilation error after file autosave

---------

Co-authored-by: Justin van der Krieken <justin@vdkrieken.com>
Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
2024-01-15 09:25:56 -06:00
Alex
d096caccac chore(web): quota enhancement (#6371)
* chore(web): quota enhancement

* show quota in user table

* update quota for single user ioption

* Add a note how to set unlimited storage

* fixed deletion doesn't update quota

* refactor relation

* fixed test

* re-refactor

* update sql

* fix e2e test

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

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

* revert e2e test

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-15 09:04:29 -06:00
renovate[bot]
2a8cb70c98 fix(deps): update dependency geo-tz to v8 (#6388)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 08:27:59 -05:00
shenlong
a09fbe5723 chore(renovate): enforce compatible flavor for guava (#6392)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-15 11:41:37 +00:00
shenlong
6ee9c8277f chore(dep): remove unused badges dep (#6384)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-15 00:02:11 -06:00
Guillermo
f3d9196a7e fix(docs) Fix command name (#6368) 2024-01-13 14:16:17 -06:00
Thariq Shanavas
9a7f987835 fix(docs) Fix relative paths leading to broken links (#6354) 2024-01-13 14:15:32 -06:00
Daniel Dietzler
5e2aec3892 fix(web): quota usage view (#6358) 2024-01-13 14:14:24 -06:00
Mert
e2666f0e74 fix(ml): remove unused import (#6356)
* remove unused import

* formatting
2024-01-13 00:25:26 -05:00
renovate[bot]
20be42cec0 chore(deps): update machine-learning (#6302)
* chore(deps): update machine-learning

* fix typing, use new lifespan syntax

* wrap in try / finally

* move log

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2024-01-13 05:00:09 +00:00
Alex
bd5ae9f31e fix(web): wrap long word in description (#6351) 2024-01-12 19:45:17 -05:00
cfitzw
deb1f970a8 feat(server, web): quotas (#4471)
* feat: quotas

* chore: open api

* chore: update status box and upload error message

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-12 19:43:36 -05:00
Jason Rasmussen
f4edb6c4bd feat(server): track metadata extracted at (#6352) 2024-01-12 19:39:45 -05:00
Russell Tan
19e9908ee2 fix(web): show description in shared links (#4249)
* chore: rebase

* fix: re-size issue

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-12 20:27:34 +00:00
aviv926
df4af025d7 Edit and update of FAQ+new database GUI guide (#5958)
* Update added and re -arrangement of FAQ

* Erasure of addition mistakes

* Erasure of addition mistakes

* Fix broken links

* Correcting spelling errors + adding more questions

* New required fixes

* More FAQ

* Adding questions + adding a note about a directory and an explanation about adding a path in a Windows environment

* Update docs/docs/FAQ/Albums-FAQ.md

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* Update docs/docs/FAQ/Assets-FAQ.md

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* Update docs/docs/FAQ/Machine-Learning-FAQ.md

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* Update docs/docs/FAQ/Machine-Learning-FAQ.md

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* Update docs/docs/features/libraries.md

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* Corrections

* chore: updates

* import TOCinlines from all FAQ pages to one page + Corrections

* Removing privacy information + adding required Flutter version information

* Removing privacy information + adding required Flutter version information

* Revert "Removing privacy information + adding required Flutter version information"

This reverts commit da63439fd2.

* All in one page

* Guide - Remove Offline Files

* Guide - Remove Offline Files

* doc: updates

* chore: fix broken link

* docs: clean up database gui guide

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-12 14:45:41 -05:00
bo0tzz
d12a361992 fix(web): OAuth settings nits (#6330)
* fix(web): Correct oauth documentation link

* fix(web): Quotes instead of backticks
2024-01-12 18:42:59 +00:00
Daniel Dietzler
a4f49d197e refactor(web): admin settings (#6177)
* refactor admin settings

* use slots to render buttons in simplified template settings

* remove more boilerplate by looping over components

* fix: onboarding

* fix: reset/reset to default

* remove lodash since it is unecessary

* chore: standardize padding and margins

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-01-12 12:44:11 -05:00
Jason Rasmussen
2439c5ab57 refactor: open api (#6334) 2024-01-12 07:36:27 -05:00
Graham Steffaniak
a1523a9af0 docs: Update libraries.md (#6333) 2024-01-11 17:10:48 -05:00
Fynn Petersen-Frey
753292956e feat(ml): ARMNN acceleration (#5667)
* feat(ml): ARMNN acceleration for CLIP

* wrap ANN as ONNX-Session

* strict typing

* normalize ARMNN CLIP embedding

* mutex to handle concurrent execution

* make inputs contiguous

* fine-grained locking; concurrent network execution

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2024-01-11 18:26:46 +01:00
bo0tzz
29747437f6 ci: Run e2e tests on self-hosted runner (#6296) 2024-01-11 09:48:49 -06:00
shenlong
4f942bc182 fix(mobile): copy shared link (#6310)
* fix(mobile): copy shared link

* fix: handle trailing slash

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-10 09:32:09 -06:00
shenlong
0e0a472de1 fix(mobile): ensure notifier is mounted before updating state (#6308)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-01-10 09:31:54 -06:00
waclaw66
902977f165 fix(server): exif gps decoding (#6138) 2024-01-10 07:36:54 -05:00
Jason Rasmussen
08fcce9e90 fix(web): copy shared link (#6309) 2024-01-10 04:10:06 +00:00
Jason Rasmussen
bf1dd36fa9 refactor(server): split api and jobs into separate e2e suites (#6307)
* refactor: domain and infra modules

* refactor(server): e2e tests
2024-01-09 23:04:16 -05:00
martin
e5786b200a fix(web): large description on detail-panel (#6305) 2024-01-09 22:52:12 -05:00
Jason Rasmussen
12dc7c48c9 refactor(server): domain and infra modules (#6301) 2024-01-09 17:07:01 -05:00
renovate[bot]
26e6602ed3 fix(deps): update docs (#6284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 16:48:32 -05:00
Jason Rasmussen
61b97157ed chore: release note groups (#6297) 2024-01-09 18:55:59 +00:00
bo0tzz
58bd9c0018 fix(docs): Use absolute path for external library guide (#6290)
* fix(docs): Use absolute path for external library guide

* fix: Cursor in screenshot
2024-01-09 09:32:30 -06:00
Alex
8d1287ef15 fix(web): logout and clear user store when using back button on the change password form (#6288) 2024-01-09 09:32:23 -06:00
Daniel Dietzler
8d0a619e81 fix(web): handle trailing slash in external domain (#6253) 2024-01-08 23:06:02 -06:00
martin
29b204de57 fix(web): grid on people page (#5640)
* fix: grid on people page

* pr feedback

* wait before width is set

* fix: animation

* fix: use grid instead

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-01-08 22:04:38 -05:00
indam
bf8e2966c4 docs: update Chinese README (#6245)
* update Chinese README

* update Chinese README, update features
2024-01-08 10:31:54 -06:00
500 changed files with 19352 additions and 42232 deletions

View File

@@ -8,7 +8,7 @@ machine-learning/
misc/
mobile/
server/node_modules
server/node_modules/
server/coverage/
server/.reverse-geocoding-dump/
server/upload/
@@ -19,7 +19,10 @@ web/coverage/
web/.svelte-kit
web/build/
cli/node_modules
cli/node_modules/
cli/.reverse-geocoding-dump/
cli/upload/
cli/dist/
cli/dist/
open-api/typescript-sdk/node_modules/
open-api/typescript-sdk/build/

13
.gitattributes vendored
View File

@@ -8,14 +8,9 @@ mobile/openapi/.openapi-generator/FILES linguist-generated=true
mobile/lib/**/*.g.dart -diff -merge
mobile/lib/**/*.g.dart 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
open-api/typescript-sdk/client/**/*.md -diff -merge
open-api/typescript-sdk/client/**/*.md linguist-generated=true
open-api/typescript-sdk/client/**/*.ts -diff -merge
open-api/typescript-sdk/client/**/*.ts linguist-generated=true
*.sh text eol=lf

View File

@@ -1,7 +1,5 @@
name: Report an issue with Immich
description: Report an issue with Immich
labels: ["bug", "need triage"]
title: "[BUG] <title>"
body:
- type: markdown
attributes:

28
.github/release.yml vendored
View File

@@ -1,30 +1,42 @@
changelog:
categories:
- title: Breaking Changes 🛠
- title: ⚠️ Breaking Changes
labels:
- breaking-change
- title: Server
- title: 🗄️ Server
labels:
- 🗄server
- title: Mobile
- title: 📱 Mobile
labels:
- 📱mobile
- title: Web
- title: 🖥️ Web
labels:
- 🖥web
- title: Machine Learning
- title: 🧠 Machine Learning
labels:
- 🧠machine-learning
- title: CLI
- title: ⚡ CLI
labels:
- cli
- title: Documentation
- title: 📓 Documentation
labels:
- documentation
- title: Dependency updates
- title: 🔨 Build
labels:
- deployment
- title: 🤖 Dependencies
labels:
- dependencies
- renovate
- title: Other changes
labels:
- "*"

View File

@@ -10,9 +10,26 @@ concurrency:
cancel-in-progress: true
jobs:
e2e-tests:
name: Server (e2e)
runs-on: ubuntu-latest
server-e2e-api:
name: Server (e2e-api)
runs-on: mich
defaults:
run:
working-directory: ./server
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run npm install
run: npm ci
- name: Run e2e tests
run: npm run e2e:api
server-e2e-jobs:
name: Server (e2e-jobs)
runs-on: mich
steps:
- name: Checkout code
@@ -21,7 +38,7 @@ jobs:
submodules: "recursive"
- name: Run e2e tests
run: make test-server-e2e
run: make server-e2e-jobs
doc-tests:
name: Docs
@@ -41,10 +58,6 @@ jobs:
run: npm run format
if: ${{ !cancelled() }}
- name: Run tsc
run: npm run check
if: ${{ !cancelled() }}
- name: Run build
run: npm run build
if: ${{ !cancelled() }}
@@ -90,10 +103,14 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Run npm install in cli
- name: Run setup typescript-sdk
run: npm ci && npm run build
working-directory: ./open-api/typescript-sdk
- name: Run npm install (cli)
run: npm ci
- name: Run npm install in server
- name: Run npm install (server)
run: npm ci
working-directory: ./server
@@ -126,10 +143,14 @@ jobs:
with:
submodules: "recursive"
- name: Run npm install in cli
- name: Run setup typescript-sdk
run: npm ci && npm run build
working-directory: ./open-api/typescript-sdk
- name: Run npm install (cli)
run: npm ci
- name: Run npm install in server
- name: Run npm install (server)
run: npm ci
working-directory: ./server
@@ -147,6 +168,10 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Run setup typescript-sdk
run: npm ci && npm run build
working-directory: ./open-api/typescript-sdk
- name: Run npm install
run: npm ci
@@ -220,14 +245,14 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Run API generation
run: npm --prefix server run api:generate
run: make open-api
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
id: verify-changed-files
with:
files: |
mobile/openapi
web/src/api/open-api
open-api/typescript-sdk
- name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'
run: |

6
.gitignore vendored
View File

@@ -1,3 +1,5 @@
**/node_modules/**
.DS_Store
.vscode/*
!.vscode/launch.json
@@ -12,3 +14,7 @@ mobile/gradle.properties
mobile/openapi/pubspec.lock
mobile/*.jks
mobile/libisar.dylib
open-api/typescript-sdk/build
mobile/android/fastlane/report.xml
mobile/ios/fastlane/report.xml

View File

@@ -16,8 +16,8 @@ stage:
pull-stage:
docker compose -f ./docker/docker-compose.staging.yml pull
test-server-e2e:
docker compose -f ./server/test/docker-compose.server-e2e.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
server-e2e-jobs:
docker compose -f ./server/e2e/docker-compose.server-e2e.yml up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server --remove-orphans --build
prod:
docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans
@@ -25,8 +25,15 @@ prod:
prod-scale:
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
api:
npm --prefix server run api:generate
.PHONY: open-api
open-api:
cd ./open-api && bash ./bin/generate-open-api.sh
open-api-dart:
cd ./open-api && bash ./bin/generate-open-api.sh dart
open-api-typescript:
cd ./open-api && bash ./bin/generate-open-api.sh typescript
sql:
npm --prefix server run sql:generate

View File

@@ -17,7 +17,7 @@
</p>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Main Screenshot">
<img src="design/immich-screenshots.png" title="界面截图">
</a>
<br/>
@@ -32,6 +32,7 @@
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_ru_RU.md">Русский</a>
</p>
## 免责声明
@@ -39,7 +40,7 @@
- ⚠️ 本项目正在 **非常活跃** 地开发中。
- ⚠️ 可能存在 bug 或者随时有重大变更。
- ⚠️ **不要把本软件作为您存储照片或视频的唯一方式。**
- ⚠️ 为了您宝贵的照片与视频,始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案!
- ⚠️ 为了您宝贵的照片与视频,始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案!
## 目录
@@ -74,40 +75,41 @@
# 功能特性
| 功能特性 | 移动端 | 网页端 |
| ------------------------------------------- | ------- | --- |
| 上传并查看照片和视频 | 是 | 是 |
| 软件运行时自动备份 | 是 | N/A |
| 选择需要备份的相册 | 是 | N/A |
| 下载照片和视频到本地 | 是 | 是 |
| 多用户支持 | 是 | 是 |
| 相册 | 是 | 是 |
| 共享相册 | 是 | 是 |
| 可拖动的快速导航栏 | 是 | 是 |
| 支持RAW格式 (HEIC, HEIF, DNG, Apple ProRaw) | 是 | 是 |
| 元数据视图EXIF, 地图) | 是 | 是 |
| 通过元数据、对象和标签进行搜索 | | 是 |
| 管理功能(用户管理) | 否 | 是 |
| 后台备份 | 是 | N/A |
| 虚拟滚动 | 是 | 是 |
| OAuth 支持 | | 是 |
| API Keys|N/A|是|
| 实况照片备份和查看 | 仅 iOS | 是 |
|用户自定义存储结构|是|是|
|公共分享|否|是|
|归档与收藏功能|是|是|
|全局地图|否|是|
|好友分享|是|是|
|人像识别与分组|是|是|
|回忆(那年今日)|是|是|
|离线支持|是|否|
|只读相册|是|是|
| 功能特性 | 移动端 | 网页端 |
|---------------------------------------------|--------|--------|
| 上传并查看照片和视频 | 是 | 是 |
| 软件运行时自动备份 | 是 | N/A |
| 选择需要备份的相册 | 是 | N/A |
| 下载照片和视频到本地 | 是 | 是 |
| 多用户支持 | 是 | 是 |
| 相册与共享相册 | 是 | 是 |
| 可拖动的快速导航栏 | 是 | 是 |
| 支持RAW格式 | 是 | 是 |
| 元数据视图EXIF、地图 | 是 | 是 |
| 通过元数据、对象、人脸和标签进行搜索 | 是 | 是 |
| 管理功能(用户管理) | | 是 |
| 后台备份 | 是 | N/A |
| 虚拟滚动 | 是 | 是 |
| OAuth 支持 | 是 | 是 |
| API Keys | N/A | 是 |
| 实况照片备份和查看 | 是 | 是 |
| 用户自定义存储结构 | 是 | 是 |
| 公共分享 | 否 | 是 |
| 归档与收藏功能 | 是 | 是 |
| 足迹地图 | 是 | 是 |
| 好友分享 | 是 | 是 |
| 人脸识别与分组 | 是 | 是 |
| 回忆(那年今日) | 是 | 是 |
| 离线支持 | 是 | 否 |
| 只读相册 | 是 | 是 |
| 照片堆叠 | 是 | 是 |
# 支持本项目
我已经致力于本项目并且我会持续更新文档、新增功能和修复问题。但是独木不成林,我需要您给予我坚持下去的动力。
我已经致力于本项目并且我会持续更新文档、新增功能和修复问题。但是独木不成林,我需要您给予我坚持下去的动力。
就像我在 [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) 节目里说的一样这是我和团队的一项艰巨任务。并且我希望某一天我能够全职开发本项目,在此我请求您能够助我梦想成真。
如果您使用了本项目一段时间,并且觉得上面的话有道理,那么请您考虑通过下列任一方式支持我吧。
@@ -118,3 +120,9 @@
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- 比特币: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
## 贡献者
<a href="https://github.com/alextran1502/immich/graphs/contributors">
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
</a>

View File

@@ -5,7 +5,6 @@ node_modules
.env
.env.*
!.env.example
src/api/open-api
*.md
*.json
coverage

View File

@@ -1,4 +1,4 @@
FROM ghcr.io/immich-app/base-server-dev:20231228@sha256:e631113b47c7e16a06ca47d3a99bdf269e831dfa4b94f6f4cc923781fa82c4e3 as test
FROM ghcr.io/immich-app/base-server-dev:20240111@sha256:5acf773796f93c7a3216ffdbdb3604dc812f2b2317b84a1b57b65674826b746a as test
WORKDIR /usr/src/app/server
COPY server/package.json server/package-lock.json ./
@@ -10,7 +10,7 @@ COPY cli/package.json cli/package-lock.json ./
RUN npm ci
COPY ./cli/ .
FROM ghcr.io/immich-app/base-server-prod:20231228@sha256:e51e418d904124f368eca84b504414e40c5b55f9990be043d1749fdf5d1a045c
FROM ghcr.io/immich-app/base-server-prod:20240111@sha256:e917605008977f68dc3b6f7879c264cae4bff6c4186b119a6e114a60f8f5a354
VOLUME /usr/src/app/upload

415
cli/package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "2.0.6",
"license": "MIT",
"dependencies": {
"@immich/sdk": "file:../open-api/typescript-sdk",
"axios": "^1.6.2",
"byte-size": "^8.1.1",
"cli-progress": "^3.12.0",
@@ -48,9 +49,21 @@
"typescript": "^5.0.0"
}
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.92.1",
"license": "MIT",
"dependencies": {
"axios": "^0.27.2"
},
"devDependencies": {
"@types/node": "^20.11.0",
"typescript": "^5.3.3"
}
},
"../server": {
"name": "immich",
"version": "1.91.4",
"version": "1.92.1",
"dev": true,
"license": "UNLICENSED",
"dependencies": {
@@ -75,10 +88,10 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"exiftool-vendored": "~23.5.0",
"exiftool-vendored.pl": "12.70",
"exiftool-vendored": "~24.3.0",
"exiftool-vendored.pl": "12.72",
"fluent-ffmpeg": "^2.1.2",
"geo-tz": "^7.0.7",
"geo-tz": "^8.0.0",
"glob": "^10.3.3",
"handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0",
@@ -944,6 +957,10 @@
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"node_modules/@immich/sdk": {
"resolved": "../open-api/typescript-sdk",
"link": true
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -1708,9 +1725,9 @@
}
},
"node_modules/@types/node": {
"version": "20.10.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz",
"integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==",
"version": "20.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -1777,16 +1794,16 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz",
"integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz",
"integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/type-utils": "6.17.0",
"@typescript-eslint/utils": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/type-utils": "6.18.1",
"@typescript-eslint/utils": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -1812,13 +1829,13 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0"
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1829,9 +1846,9 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1842,13 +1859,13 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1870,17 +1887,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz",
"integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
"integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.18.1",
"semver": "^7.5.4"
},
"engines": {
@@ -1895,12 +1912,12 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -1936,15 +1953,15 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz",
"integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz",
"integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1964,13 +1981,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0"
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1981,9 +1998,9 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1994,13 +2011,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -2022,12 +2039,12 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -2080,13 +2097,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz",
"integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz",
"integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/utils": "6.17.0",
"@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/utils": "6.18.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -2107,13 +2124,13 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0"
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -2124,9 +2141,9 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -2137,13 +2154,13 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -2165,17 +2182,17 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz",
"integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
"integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.18.1",
"semver": "^7.5.4"
},
"engines": {
@@ -2190,12 +2207,12 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -2564,9 +2581,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz",
"integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==",
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
"dependencies": {
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
@@ -3516,9 +3533,9 @@
}
},
"node_modules/eslint-plugin-jest": {
"version": "27.6.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.1.tgz",
"integrity": "sha512-WEYkyVXD9NlmFBKvrkmzrC+C9yZoz5pAml2hO19PlS3spJtoiwj4p2u8spd/7zx5IvRsZsCmsoImaAvBB9X93Q==",
"version": "27.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.2.tgz",
"integrity": "sha512-CI1AlKrsNhYFoP48VU8BVWOi7+qHTq4bRxyUlGjeU8SfFt8abjXhjOuDzUoMp68DoXIx17KpNpIkMrl4s4ZW0g==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^5.10.0"
@@ -3541,9 +3558,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.2.tgz",
"integrity": "sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
"integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
"dev": true,
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
@@ -8081,6 +8098,14 @@
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"dev": true
},
"@immich/sdk": {
"version": "file:../open-api/typescript-sdk",
"requires": {
"@types/node": "^20.11.0",
"axios": "^0.27.2",
"typescript": "^5.3.3"
}
},
"@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -8712,9 +8737,9 @@
}
},
"@types/node": {
"version": "20.10.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz",
"integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==",
"version": "20.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==",
"dev": true,
"requires": {
"undici-types": "~5.26.4"
@@ -8783,16 +8808,16 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz",
"integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz",
"integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==",
"dev": true,
"requires": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/type-utils": "6.17.0",
"@typescript-eslint/utils": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/type-utils": "6.18.1",
"@typescript-eslint/utils": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -8802,29 +8827,29 @@
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0"
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1"
}
},
"@typescript-eslint/types": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -8834,27 +8859,27 @@
}
},
"@typescript-eslint/utils": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz",
"integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
"integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.18.1",
"semver": "^7.5.4"
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1"
}
},
@@ -8879,42 +8904,42 @@
}
},
"@typescript-eslint/parser": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz",
"integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz",
"integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0"
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1"
}
},
"@typescript-eslint/types": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -8924,12 +8949,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1"
}
},
@@ -8964,41 +8989,41 @@
}
},
"@typescript-eslint/type-utils": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz",
"integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz",
"integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/utils": "6.17.0",
"@typescript-eslint/typescript-estree": "6.18.1",
"@typescript-eslint/utils": "6.18.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz",
"integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz",
"integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0"
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1"
}
},
"@typescript-eslint/types": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz",
"integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz",
"integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz",
"integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz",
"integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/visitor-keys": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/visitor-keys": "6.18.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -9008,27 +9033,27 @@
}
},
"@typescript-eslint/utils": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz",
"integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz",
"integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.17.0",
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/typescript-estree": "6.17.0",
"@typescript-eslint/scope-manager": "6.18.1",
"@typescript-eslint/types": "6.18.1",
"@typescript-eslint/typescript-estree": "6.18.1",
"semver": "^7.5.4"
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz",
"integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==",
"version": "6.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz",
"integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.17.0",
"@typescript-eslint/types": "6.18.1",
"eslint-visitor-keys": "^3.4.1"
}
},
@@ -9300,9 +9325,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz",
"integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==",
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
"requires": {
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
@@ -10007,18 +10032,18 @@
"requires": {}
},
"eslint-plugin-jest": {
"version": "27.6.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.1.tgz",
"integrity": "sha512-WEYkyVXD9NlmFBKvrkmzrC+C9yZoz5pAml2hO19PlS3spJtoiwj4p2u8spd/7zx5IvRsZsCmsoImaAvBB9X93Q==",
"version": "27.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.6.2.tgz",
"integrity": "sha512-CI1AlKrsNhYFoP48VU8BVWOi7+qHTq4bRxyUlGjeU8SfFt8abjXhjOuDzUoMp68DoXIx17KpNpIkMrl4s4ZW0g==",
"dev": true,
"requires": {
"@typescript-eslint/utils": "^5.10.0"
}
},
"eslint-plugin-prettier": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.2.tgz",
"integrity": "sha512-dhlpWc9vOwohcWmClFcA+HjlvUpuyynYs0Rf+L/P6/0iQE6vlHW9l5bkfzN62/Stm9fbq8ku46qzde76T1xlSg==",
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
"integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0",
@@ -10545,10 +10570,10 @@
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"exiftool-vendored": "~23.5.0",
"exiftool-vendored.pl": "12.70",
"exiftool-vendored": "~24.3.0",
"exiftool-vendored.pl": "12.72",
"fluent-ffmpeg": "^2.1.2",
"geo-tz": "^7.0.7",
"geo-tz": "^8.0.0",
"glob": "^10.3.3",
"handlebars": "^4.7.8",
"i18n-iso-countries": "^7.6.0",

View File

@@ -12,6 +12,7 @@
"cli"
],
"dependencies": {
"@immich/sdk": "file:../open-api/typescript-sdk",
"axios": "^1.6.2",
"byte-size": "^8.1.1",
"cli-progress": "^3.12.0",
@@ -57,7 +58,7 @@
"format": "prettier --check .",
"format:fix": "prettier --write .",
"check": "tsc --noEmit",
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules' jest --config test/e2e/jest-e2e.json --runInBand"
"test:e2e": "jest --config test/e2e/jest-e2e.json --runInBand"
},
"jest": {
"clearMocks": true,

View File

@@ -9,7 +9,7 @@ import {
ServerInfoApi,
SystemConfigApi,
UserApi,
} from './open-api';
} from '@immich/sdk';
import { ApiConfiguration } from '../cores/api-configuration';
import FormData from 'form-data';

View File

@@ -1,72 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.92.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"
}
}

View File

@@ -1,150 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.92.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);
};
}

View File

@@ -1,101 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.92.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

@@ -2,7 +2,7 @@ import { ImmichApi } from '../api/client';
import { SessionService } from '../services/session.service';
import { LoginError } from '../cores/errors/login-error';
import { exit } from 'node:process';
import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api';
import { ServerVersionResponseDto, UserResponseDto } from '@immich/sdk';
import { BaseOptionsDto } from 'src/cores/dto/base-options-dto';
export abstract class BaseCommand {

View File

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

View File

@@ -1,6 +1,6 @@
import { BaseCommand } from '../cli/base-command';
export default class Logout extends BaseCommand {
export class Logout extends BaseCommand {
public static readonly description = 'Logout and remove persisted credentials';
public async run(): Promise<void> {

View File

@@ -1,6 +1,6 @@
import { BaseCommand } from '../cli/base-command';
export default class ServerInfo extends BaseCommand {
export class ServerInfo extends BaseCommand {
public async run() {
await this.connect();
const { data: versionInfo } = await this.immichApi.serverInfoApi.getServerVersion();

View File

@@ -6,10 +6,10 @@ import fs from 'node:fs';
import cliProgress from 'cli-progress';
import byteSize from 'byte-size';
import { BaseCommand } from '../cli/base-command';
import axios, { AxiosRequestConfig } from 'axios';
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import FormData from 'form-data';
export default class Upload extends BaseCommand {
export class Upload extends BaseCommand {
uploadLength!: number;
public async run(paths: string[], options: UploadOptionsDto): Promise<void> {
@@ -107,6 +107,8 @@ export default class Upload extends BaseCommand {
const formData = asset.getUploadFormData();
const res = await this.uploadAsset(formData);
existingAssetId = res.data.id;
uploadCounter++;
totalSizeUploaded += asset.fileSize;
}
if ((options.album || options.albumName) && asset.albumName !== undefined) {
@@ -127,9 +129,6 @@ export default class Upload extends BaseCommand {
}
}
}
totalSizeUploaded += asset.fileSize;
uploadCounter++;
}
sizeSoFar += asset.fileSize;
@@ -172,7 +171,7 @@ export default class Upload extends BaseCommand {
}
}
private async uploadAsset(data: FormData): Promise<axios.AxiosResponse> {
private async uploadAsset(data: FormData): Promise<AxiosResponse> {
const url = this.immichApi.apiConfiguration.instanceUrl + '/asset/upload';
const config: AxiosRequestConfig = {

View File

@@ -1,10 +1,10 @@
#! /usr/bin/env node
import { Option, Command } from 'commander';
import Upload from './commands/upload';
import ServerInfo from './commands/server-info';
import LoginKey from './commands/login/key';
import Logout from './commands/logout';
import { Upload } from './commands/upload';
import { ServerInfo } from './commands/server-info';
import { LoginKey } from './commands/login/key';
import { Logout } from './commands/logout';
import { version } from '../package.json';
import path from 'node:path';

View File

@@ -16,10 +16,9 @@ import {
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', () => {
jest.mock('@immich/sdk', () => {
return {
__esModule: true,
...jest.requireActual('../api/open-api'),
...jest.requireActual('@immich/sdk'),
UserApi: jest.fn().mockImplementation(() => {
return { getMyUserInfo: mockUserInfo };
}),

View File

@@ -7,7 +7,7 @@
"testRegex": ".e2e-spec.ts$",
"testTimeout": 6000000,
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
"^.+\\.ts$": "ts-jest"
},
"collectCoverageFrom": [
"<rootDir>/src/**/*.(t|j)s",

View File

@@ -1,8 +1,8 @@
import { api } from '@test/api';
import { restoreTempFolder, testApp } from 'immich/test/test-utils';
import { LoginResponseDto } from 'src/api/open-api';
import { APIKeyCreateResponseDto } from '@app/domain';
import LoginKey from 'src/commands/login/key';
import { api } from '@test/../e2e/api/client';
import { restoreTempFolder, testApp } from '@test/../e2e/jobs/utils';
import { LoginResponseDto } from '@immich/sdk';
import { LoginKey } from 'src/commands/login/key';
import { LoginError } from 'src/cores/errors/login-error';
import { CLI_BASE_OPTIONS, spyOnConsole } from 'test/cli-test-utils';

View File

@@ -1,8 +1,8 @@
import { api } from '@test/api';
import { restoreTempFolder, testApp } from 'immich/test/test-utils';
import { LoginResponseDto } from 'src/api/open-api';
import ServerInfo from 'src/commands/server-info';
import { APIKeyCreateResponseDto } from '@app/domain';
import { api } from '@test/../e2e/api/client';
import { restoreTempFolder, testApp } from '@test/../e2e/jobs/utils';
import { LoginResponseDto } from '@immich/sdk';
import { ServerInfo } from 'src/commands/server-info';
import { CLI_BASE_OPTIONS, spyOnConsole } from 'test/cli-test-utils';
describe(`server-info (e2e)`, () => {

View File

@@ -37,7 +37,6 @@ export default async () => {
}
process.env.NODE_ENV = 'development';
process.env.IMMICH_TEST_ENV = 'true';
process.env.IMMICH_CONFIG_FILE = path.normalize(`${__dirname}/../../../server/test/e2e/immich-e2e-config.json`);
process.env.IMMICH_CONFIG_FILE = path.normalize(`${__dirname}/../../../server/e2e/jobs/immich-e2e-config.json`);
process.env.TZ = 'Z';
};

View File

@@ -1,8 +1,8 @@
import { api } from '@test/api';
import { IMMICH_TEST_ASSET_PATH, restoreTempFolder, testApp } from 'immich/test/test-utils';
import { LoginResponseDto } from 'src/api/open-api';
import Upload from 'src/commands/upload';
import { APIKeyCreateResponseDto } from '@app/domain';
import { api } from '@test/../e2e/api/client';
import { IMMICH_TEST_ASSET_PATH, restoreTempFolder, testApp } from '@test/../e2e/jobs/utils';
import { LoginResponseDto } from '@immich/sdk';
import { Upload } from 'src/commands/upload';
import { CLI_BASE_OPTIONS, spyOnConsole } from 'test/cli-test-utils';
describe(`upload (e2e)`, () => {

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "Node16",
"module": "commonjs",
"strict": true,
"declaration": true,
"removeComments": true,
@@ -9,7 +9,6 @@
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2021",
"moduleResolution": "node16",
"sourceMap": true,
"outDir": "./dist",
"incremental": true,

View File

@@ -15,6 +15,7 @@ x-server-build: &server-common
restart: always
volumes:
- ../server:/usr/src/app
- ../open-api:/usr/src/open-api
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
- /usr/src/app/node_modules
@@ -65,6 +66,7 @@ services:
- 24678:24678
volumes:
- ../web:/usr/src/app
- ../open-api/:/usr/src/open-api/
- /usr/src/app/node_modules
ulimits:
nofile:

11
docker/mlaccel-armnn.yml Normal file
View File

@@ -0,0 +1,11 @@
version: "3.8"
# ML acceleration on supported Mali ARM GPUs using ARM-NN
services:
mlaccel:
devices:
- /dev/mali0:/dev/mali0
volumes:
- /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver)
- /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required)

View File

@@ -1,122 +0,0 @@
---
sidebar_position: 7
---
# FAQ
### What is the difference between the cloud icon on the mobile app?
| Icon | Description |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| ![cloud](/img/cloud.svg) | Asset is only available in the cloud and was uploaded from some other device (like the web client) or was deleted from this device after upload |
| ![cloud-cross](/img/cloud-off.svg) | Asset is only available locally and has not yet been backed up |
| ![cloud-done](/img/cloud-done.svg) | Asset was uploaded from this device and is now backed up in the cloud/server and still available in original on the device |
### Can I add my existing photo library?
Yes, with an [external library](/docs/features/libraries.md).
### Why are only photos and not videos being uploaded to Immich?
This often happens when using a reverse proxy or cloudflare tunnel in front of Immich. Make sure to set your reverse proxy to allow large POST requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Cloudflare tunnels are limited to 100 mb file sizes. Also check the disk space of your reverse proxy, in some cases proxies caches requests to disk before passing them on, and if disk space runs out the request fails.
### Why is Immich slow on low-memory systems like the Raspberry Pi?
Immich optionally uses machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/docs/FAQ#how-can-i-lower-immichs-cpu-usage) this or [disable](/docs/FAQ.md#how-can-i-disable-machine-learning) machine learning entirely.
### How can I lower Immich's CPU usage?
The initial backup is the most intensive due to the number of jobs running. The most CPU-intensive ones are transcoding and machine learning jobs (Tag Images, Smart Search, Recognize Faces), and to a lesser extent thumbnail generation. Here are some ways to lower their CPU usage:
- Lower the job concurrency for these jobs to 1.
- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2.
- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good.
- You _must_ re-run the Recognize Faces job for all images after this for facial recognition on new images to work properly.
- If these changes are not enough, see [below](/docs/FAQ.md#how-can-i-disable-machine-learning) for how you can disable machine learning.
### How can I disable machine learning?
:::info
Disabling machine learning will result in a poor experience for searching and the 'Explore' page, as these are reliant on it to work as intended.
:::
Machine learning can be disabled under Settings > Machine Learning Settings, either entirely or by model type. For instance, you can choose to disable smart search with CLIP, but keep facial recognition enabled. This means that the machine learning service will only process the enabled jobs.
However, disabling all jobs will not disable the machine learning service itself. To prevent it from starting up at all in this case, you can comment out the `immich-machine-learning` section of the docker-compose.yml.
### I'm getting errors about models being corrupt or failing to download. What do I do?
You can delete the model cache volume, which is where models are downloaded. This will give the service a clean environment to download the model again.
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the Storage Migration Job, available on the [Jobs](/docs/administration/jobs.md) page.
### In the uploads folder, why are photos stored in the wrong date?
This is fixed by running the storage migration job.
### Why are there so many thumbnail generation jobs?
Immich generates three thumbnails for each asset (blurred, small, and large), as well as a thumbnail for each recognized face.
### How can I see Immich logs?
Most Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/docs/guides/docker-help.md)
### How can I run Immich as a non-root user?
1. Set the `PUID`/`PGID` environment variables (in `.env`).
2. Set the corresponding `user` argument in `docker-compose` for each service.
3. Add an additional volume to `immich-microservices` that mounts internally to `/usr/src/app/.reverse-geocoding-dump`.
The non-root user/group needs read/write access to the volume mounts, including `UPLOAD_LOCATION`.
### How can I reset the admin password?
The admin password can be reset by running the [reset-admin-password](/docs/administration/server-commands.md) command on the immich-server.
### How can I backup data from Immich?
See [backup and restore](/docs/administration/backup-and-restore.md).
### How can I **purge** data from Immich?
Data for Immich comes in two forms:
1. **Metadata** stored in a postgres database, persisted via the `pg_data` volume
2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder.
To remove the **Metadata** you can stop Immich and delete the volume.
```bash title="Remove Immich (containers and volumes)"
docker-compose down -v
```
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.
### 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.

311
docs/docs/FAQ.mdx Normal file
View File

@@ -0,0 +1,311 @@
# FAQ
## User
### How can I reset the admin password?
The admin password can be reset by running the [reset-admin-password](/docs/administration/server-commands.md) command on the immich-server.
### How can I see list of all users in Immich?
You can see the list of all users by running [list-users](/docs/administration/server-commands.md) Command on the Immich-server.
---
## Mobile App
### What is the difference between the cloud icons on the mobile app?
| Icon | Description |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| ![cloud](/img/cloud.svg) | Asset is only available in the cloud and was uploaded from some other device (like the web client) or was deleted from this device after upload |
| ![cloud-cross](/img/cloud-off.svg) | Asset is only available locally and has not yet been backed up |
| ![cloud-done](/img/cloud-done.svg) | Asset was uploaded from this device and is now backed up to the server; the original file is still on the device |
### I cannot log into the application after an update. What can I do?
First, verify that the mobile app and server are both running the same version (major and minor).
:::note
App store updates sometimes take longer because the stores (play store; Google and app store; Apple)
need to approve the update first which may take some time.
:::
If you still cannot login to the app, try the following:
- Check the mobile logs
- Make sure login credentials are correct by logging in on the web app
---
## Assets
### Can I add my existing photo library?
Yes, with an [External Library](/docs/features/libraries.md).
### What happens to existing files after I choose a new [Storage Template](/docs/administration/storage-template.mdx)?
Template changes will only apply to _new_ assets. To retroactively apply the template to previously uploaded assets, run the Storage Migration Job, available on the [Jobs](/docs/administration/jobs.md) page.
### Why are only photos and not videos being uploaded to Immich?
This often happens when using a reverse proxy (such as nginx or Cloudflare tunnel) in front of Immich. Make sure to set your reverse proxy to allow large `POST` requests. In `nginx`, set `client_max_body_size 50000M;` or similar. Also check the disk space of your reverse proxy, in some cases proxies cache requests to disk before passing them on, and if disk space runs out the request fails.
### Why are some photos stored in the file system with the wrong date?
There are a few different scenarios that can lead to this situation. The solution is to simply run the storage migration job again. The job is only _automatically_ run once per asset, after upload. If metadata extraction originally failed, the jobs were cleared/cancelled, etc. the job may not have run automatically the first time.
### How can I hide photos from the timeline?
You can _archive_ them.
### How can I backup data from Immich?
See [Backup and Restore](/docs/administration/backup-and-restore.md).
### Does Immich support reading existing face tag metadata?
No, it currently does not.
### Does Immich support filtering of NSFW images?
No, it currently does not, but there is an [open discussion about it On Github](https://github.com/immich-app/immich/discussions/2451). You can submit a pull request or vote for the discussion.
### Why are there so many thumbnail generation jobs?
There are three thubmanil jobs for each asset:
- Blurred (thumbhash)
- Small (webp)
- Large (jpeg)
Also, there are additional jobs for person (face) thumbnails.
### What happens if an asset exists in more than one account?
There are no requirements for assets to be unique across users. If multiple users upload the same image they are processed as if they were distinct assets and jobs run and thumbnails are generated accordingly.
### How can I delete transcoded videos without deleting the original?
The transcode of an asset can be deleted by setting a transcode policy that makes it unnecessary, then running a transcoding job for that asset. This can be done on a per-asset basis by starting a transcoding job for an asset (with the _Refresh encoded videos_ button in the asset viewer options. Or, for all assets by running transcoding jobs for all assets.
To update the transcode policy, navigate to Administration > Video Transcoding Settings > Transcoding Policy and select a policy from the drop-down. This policy will determine whether an existing transcode will be deleted or overwritten in the transcoding job. If a video should be transcoded according to this policy, an existing transcode is overwritten. If not, then it is deleted.
:::note
For example, say you have existing transcodes with the policy "Videos higher than normal resolution or not in the desired format" and switch to a narrower policy: "Videos not in the desired format". If an asset was only transcoded due to its resolution, then running a transcoding job for it will now delete the existing transcode. This is because resolution is no longer part of the transcode policy and the transcode is unnecessary as a result. Likewise, if you set the policy to "Don't transcode any videos" and run transcoding jobs for all assets, this will delete all existing transcodes as they are all unnecessary.
:::
### Is it possible to compress images during backup?
No. Our golden rule is that the original assets should always be untouched, so we don't think this feature is a good fit for Immich.
### How can I move all data (photos, persons, albums) from one user to another?
This is not officially supported, but can be accomplished with some database updates. 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 do it with the steps described here, this is not for you.
:::
<details>
<summary>Steps</summary>
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.
</details>
---
## Albums
### Can I keep my existing album structure while importing assets into Immich?
Yes. You can by use [Immich CLI](/docs/features/command-line-interface) along with the `--album` flag.
### Is there a way to reorder photos within an album?
No, not yet. For updates on this planned feature, follow the [GitHub discussion](https://github.com/immich-app/immich/discussions/1689),
---
## External Library
### Can I add an external library while keeping the existing albums structure?
We haven't put in an official mechanism to create albums from external libraries at the moment., but there are some [workarounds from the community](https://github.com/immich-app/immich/discussions/4279) which you can find here to help you achieve that.
### What happens to duplicates in external libraries?
Duplicate checking only exists for upload libraries, using the file hash. Furthermore, duplicate checking is not global, but _per library_. Therefore, a situation where the same file appears twice in the timeline is possible, especially for external libraries.
---
## Machine Learning
### How does smart search work?
Immich uses CLIP models, for more information about CLIP and its capabilities read about it [here](https://openai.com/research/clip).
### How does facial recognition work?
For face detection and recognition, Immich uses [InsightFace models](https://github.com/deepinsight/insightface/tree/master/model_zoo).
### How can I disable machine learning?
:::info
Disabling machine learning will result in a poor experience for searching and the 'Explore' page, as these are reliant on it to work as intended.
:::
Machine learning can be disabled under Administration > Settings > Machine Learning Settings, either entirely or by model type. For instance, you can choose to disable smart search with CLIP, but keep facial recognition enabled. This means that the machine learning service will only process the enabled jobs.
However, disabling all jobs will not disable the machine learning service itself. To prevent it from starting up at all in this case, you can comment out the `immich-machine-learning` section of the docker-compose.yml.
### I'm getting errors about models being corrupt or failing to download. What do I do?
You can delete the model cache volume, which is where models are downloaded to. This will give the service a clean environment to download the model again.
### Why did Immich decide to remove object detection?
The feature added keywords to images for metadata search, but wasn't used for smart search. Smart search made it unnecessary as it isn't limited to exact keywords. Combined with it causing crashes on some devices, using many dependencies and causing user confusion as to how search worked, it was better to remove the job altogether.
For more info see [here](https://github.com/immich-app/immich/pull/5903)
### Can I use a custom CLIP model?
No, this is not supported. Only models listed in the [Huggingface](https://huggingface.co/immich-app) are compatible. Feel free to make a feature request if there's a model not listed here that you think should be added.
### I want to be able to search in other languages besides English. How can I do that?
You can change to a multilingual model listed [here](https://huggingface.co/collections/immich-app/multilingual-clip-654eb08c2382f591eeb8c2a7) by going to Administration > Machine Learning Settings > Smart Search and replacing the name of the model. Be sure to re-run Smart Search on all assets after this change. You can then search in over 100 languages.
:::note
Feel free to make a feature request if there's a model you want to use that isn't in [Immich Huggingface list](https://huggingface.co/immich-app).
:::
### Does Immich support Facial Recognition for videos ?
This is not currently implemented, but may be in the future.
On the other hand, Immich does scan video thumbnails for faces, so it can perform recognition if the face is clear in the video thumbnail.
### Does Immich have animal recognition?
No.
### The immich_model-cache volume takes up a lot of space, what could be the problem?
If you installed several models and chose not to use some of them, it might be worth deleting the old models that are in immich_model-cache.
To do this you can run:
- `docker run -it --rm -v immich_model-cache:/mnt ubuntu bash`
- `cd mnt`
- `ls`
- and delete unused models with `rm -r <model_name>`.
---
## Performance
### Why is Immich slow on low-memory systems like the Raspberry Pi?
Immich optionally uses machine learning for several features. However, it can be too heavy to run on a Raspberry Pi. You can [mitigate](/docs/FAQ#can-i-lower-cpu-and-ram-usage) this or transfer to host Immich's machine-learning container on a [more powerful system](/docs/guides/remote-machine-learning) ,or [disable](/docs/FAQ#how-can-i-disable-machine-learning) machine learning entirely.
### Can I lower CPU and RAM usage?
The initial backup is the most intensive due to the number of jobs running. The most CPU-intensive ones are transcoding and machine learning jobs (Smart Search, Face Detection), and to a lesser extent thumbnail generation. Here are some ways to lower their CPU usage:
- Lower the job concurrency for these jobs to 1.
- Under Settings > Transcoding Settings > Threads, set the number of threads to a low number like 1 or 2.
- Under Settings > Machine Learning Settings > Facial Recognition > Model Name, you can change the facial recognition model to `buffalo_s` instead of `buffalo_l`. The former is a smaller and faster model, albeit not as good.
- You _must_ re-run the Face Detection job for all images after this for facial recognition on new images to work properly.
- If these changes are not enough, see [below](/docs/FAQ#how-can-i-disable-machine-learning) for how you can disable machine learning.
### Can I limit the amount of CPU and RAM usage?
By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows.
You can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit) to learn how to do this.
### How an I boost machine learning speed?
:::note
This advice improves throughput, not latency. This is to say that it will make Smart Search jobs process more quickly, but it won't make searching faster.
:::
You can increase throughput by increasing the job concurrency for machine learning jobs (Smart Search, Face Detection). With higher concurrency, the host will work on more assets in parallel. You can do this by navigating to Administration > Settings > Job Settings and increasing concurrency as needed.
:::danger
On a normal machine, 2 or 3 concurrent jobs can probably max the CPU, so if you're not hitting those maximums with, say, 30 jobs.
Note that storage speed and latency may quickly become the limiting factor; particularly when using HDDs.
Do not exaggerate with the amount of jobs because you're probably thoroughly overloading the server.
more info [here](https://discord.com/channels/979116623879368755/994044917355663450/1174711719994605708)
:::
### Why is Immich using so much of my CPU?
When a large amount of assets are uploaded to Immich it makes sense that the CPU and RAM will be heavily used due to machine learning work and creating image thumbnails after that, the percentage of CPU usage will drop to around 3-5% usage
---
## Docker
### How can I see Immich logs?
Most Immich components are typically deployed using docker. To see logs for deployed docker containers, you can use the [Docker CLI](https://docs.docker.com/engine/reference/commandline/cli/), specifically the `docker logs` command. For examples, see [Docker Help](/docs/guides/docker-help.md).
### How can I run Immich as a non-root user?
1. Set the `PUID`/`PGID` environment variables (in `.env`).
2. Set the corresponding `user` argument in `docker-compose` for each service.
3. Add an additional volume to `immich-microservices` that mounts internally to `/usr/src/app/.reverse-geocoding-dump`.
The non-root user/group needs read/write access to the volume mounts, including `UPLOAD_LOCATION`.
### How can I **purge** data from Immich?
Data for Immich comes in two forms:
1. **Metadata** stored in a postgres database, persisted via the `pg_data` volume
2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder.
To remove the **Metadata** you can stop Immich and delete the volume.
```bash title="Remove Immich (containers and volumes)"
docker compose down -v
```
After removing the containers and volumes, the **Files** can be cleaned up (if necessary) from the `UPLOAD_LOCATION` by simply deleting any unwanted files or folders.
### Why does the machine learning service report workers crashing?
:::note
If the error says the worker is exiting, then this is normal. This is a feature intended to reduce RAM consumption when the service isn't being used.
:::
There are a few reasons why this can happen.
If the error mentions SIGKILL or error code 137, it most likely means the service is running out of memory. Consider either increasing the server's RAM or moving the service to a server with more RAM.
If it mentions SIGILL (note the lack of a K) or error code 132, it most likely means your server's CPU is incompatible. This is unlikely to occur on version 1.92.0 or later. Consider upgrading if your version of Immich is below that.
If your version of Immich is below 1.92.0 and the crash occurs after logs about tracing or exporting a model, consider either upgrading or disabling the Tag Objects job.

View File

@@ -1,6 +1,6 @@
# Backup and Restore
A [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution. This page provides an overview on how to backup the database and the location of user-uploaded pictures and videos. A template bash script that can be run as a cron job is provided [here](../guides/template-backup-script)
A [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution. This page provides an overview on how to backup the database and the location of user-uploaded pictures and videos. A template bash script that can be run as a cron job is provided [here](/docs/guides/template-backup-script.md)
## Database
@@ -66,3 +66,34 @@ Immich stores two types of content in the filesystem: (1) original, unmodified c
1. `UPLOAD_LOCATION/library`
1. `UPLOAD_LOCATION/upload`
1. `UPLOAD_LOCATION/profile`
**1. User-Specific Folders:**
- Each user has a unique string representing them.
- The main user is "Admin" (but only for `\library\library\`)
- Other users have different string identifiers.
- You can find your user ID in Account Account Settings > Account > User ID.
**2. Asset Types and Storage Locations:**
- **Source Assets:**
- Original assets uploaded through the browser interface&mobile&CLI.
- Stored in `\library\library\<userID>`.
- **Avatar Images:**
- User profile images.
- Stored in `\library\profile\<userID>`.
- **Thumbs Images:**
- Preview images (blurred, small, large) for each asset and thumbnails for recognized faces.
- Stored in `\library\thumbs\<userID>`.
- **Encoded Assets:**
- By default, unless otherwise specified re-encoded video assets for wider compatibility .
- Stored in `\library\encoded-video\<userID>`.
- **Files in Upload Queue (Mobile):**
- Files uploaded through mobile apps.
- Temporarily located in `\library\upload\<userID>`.
- Transferred to `\library\library\<userID>` upon successful upload.
:::danger
Do not touch the files inside these folders under any circumstances except taking a backup, changing or removing an asset can cause untracked and missing files.
You can think of it as App-Which-Must-Not-Be-Named, the only access to viewing, changing and deleting assets is only through the mobile or browser interface.
:::

View File

@@ -1,4 +1,4 @@
import StorageTemplate from '../partials/_storage-template.md';
import StorageTemplate from '/docs/partials/_storage-template.md';
# Storage Template

View File

@@ -1,5 +1,5 @@
import RegisterAdminUser from '../partials/_register-admin.md';
import UserCreate from '../partials/_user-create.md';
import RegisterAdminUser from '/docs/partials/_register-admin.md';
import UserCreate from '/docs/partials/_user-create.md';
# User Management

View File

@@ -107,4 +107,4 @@ See [Database Migrations](./database-migrations.md) for more information about h
### 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.
Immich uses [Redis](https://redis.com/) via [BullMQ](https://docs.bullmq.io/) to manage job queues. Some jobs trigger subsequent jobs. For example, Smart Search and Facial Recognition relies on thumbnail generation and automatically run after one is generated.

View File

@@ -7,11 +7,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat
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 server in development mode and run the command below to update the client SDK.
```bash
npm run api:generate # Run from the `server/` directory
make open-api
```
You can find the generated client SDK in the `web/src/api` for Typescript SDK and `mobile/openapi` for Dart SDK.
:::tip
This can also be run via `make api` from the project root directory (not in the `server` folder)
:::
You can find the generated client SDK in the `open-api/typescript-sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.

View File

@@ -6,7 +6,7 @@ When contributing code through a pull request, please check the following:
- [ ] `npm run lint` (linting via ESLint)
- [ ] `npm run format` (formatting via Prettier)
- [ ] `npm run check` (Type checking via SvelteKit)
- [ ] `npm run check:svelte` (Type checking via SvelteKit)
- [ ] `npm test` (Tests via Jest)
:::tip

View File

@@ -4,6 +4,16 @@ sidebar_position: 2
# Setup
:::note
If there's a feature you're planning to work on, just give us a heads up in [Discord](https://discord.com/channels/979116623879368755/1071165397228855327) so we can:
1. Let you know if it's something we would accept into Immich
2. Provide any guidance on how something like that would ideally be implemented
3. Ensure nobody is already working on that issue/feature so we don't duplicate effort
Thanks for being interested in contributing 😊
:::
## Environment
### Server and web app
@@ -39,7 +49,7 @@ You can access the web from `http://your-machine-ip:2283` or `http://localhost:2
### Mobile app
The mobile app `(/mobile)` will required Flutter toolchain to be installed on your system.
The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installed on your system.
Please refer to the [Flutter's official documentation](https://flutter.dev/docs/get-started/install) for more information on setting up the toolchain on your machine.

View File

@@ -10,8 +10,8 @@ Unit are run by calling `npm run test` from the `server` directory.
The backend has an end-to-end test suite that can be called with `npm run test:e2e` from the `server` directory. This will set up a dummy database inside a temporary container and run the tests against it. Setup and teardown is automatically taken care of. That test, however, can not set up all prerequisites to parse file formats, as that is very complex and error-prone. As such, this test excludes some test cases like HEIC file imports. The test suite will also print a friendly warning to remind you that not all tests are being run.
Note that there is a bug in nodejs <20.8 that causes segmentation faults when running these tests. If you run into segfaults, ensure you are using at least version 20.8.
Note that there is a bug in nodejs \<20.8 that causes segmentation faults when running these tests. If you run into segfaults, ensure you are using at least version 20.8.
To perform a full e2e test, you need to run e2e tests inside docker. The easiest way to do that is to run `make test-e2e` in the root directory. This will build and start a docker-compose consisting of the server, microservices, and a postgres database. It will then perform the tests and exit.
If you manually install the dependencies (see the DOCKERFILE) on your development machine, you can also run the full e2e tests manually by setting the `IMMICH_RUN_ALL_TESTS` environment value to true, i.e. `IMMICH_RUN_ALL_TESTS=true npm run test:e2e`.
If you manually install the dependencies (see the DOCKERFILE) on your development machine, you can also run the full e2e tests manually by setting the `IMMICH_RUN_ALL_TESTS` environment value to true, i.e. `IMMICH_RUN_ALL_TESTS=true npm run e2e:jobs`.

View File

@@ -34,9 +34,11 @@ If you add assets from an external library to an album and then move the asset t
### Deleted External Assets
Note: Either a manual or scheduled library scan must have been performed to identify offline assets before this process will work.
In all above scan methods, Immich will check if any files are missing. This can happen if files are deleted, or if they are on a storage location that is currently unavailable, like a network drive that is not mounted, or a USB drive that has been unplugged. In order to prevent accidental deletion of assets, Immich will not immediately delete an asset from the library if the file is missing. Instead, the asset will be internally marked as offline and will still be visible in the main timeline. If the file is moved back to its original location and the library is scanned again, the asset will be restored.
Finally, files can be deleted from Immich via the `Remove Offline Files` job. Any assets marked as offline will then be removed from Immich. Run this job whenever files have been deleted from the file system and you want to remove them from Immich. Note that a library scan must be performed first to mark the assets as offline.
Finally, files can be deleted from Immich via the `Remove Offline Files` job. This job can be found by the three dots menu for the associated external storage that was configured under user account settings > libraries (the same location described at [create external libraries](#create-external-libraries)). When this job is run, any assets marked as offline will then be removed from Immich. Run this job whenever files have been deleted from the file system and you want to remove them from Immich.
### Import Paths
@@ -51,6 +53,7 @@ Sometimes, an external library will not scan correctly. This can happen if the i
- Are the volumes identical between the `server` and `microservices` container?
- Are the import paths set correctly, and do they match the path set in docker-compose file?
- Are the permissions set correctly?
- Are you using forward slashes everywhere? (`/`)
If all else fails, you can always start a shell inside the container and check if the path is accessible. For example, `docker exec -it immich_microservices /bin/bash` will start a bash shell. If your import path, for instance, is `/data/import/photos`, you can check if the files are accessible by running `ls /data/import/photos`. Also check the `immich_server` container in the same way.
@@ -102,6 +105,7 @@ First, we need to plan how we want to organize the libraries. The christmas trip
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
immich-microservices:
@@ -110,6 +114,7 @@ First, we need to plan how we want to organize the libraries. The christmas trip
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
```
:::tip
@@ -125,6 +130,14 @@ Only an admin can do this.
- Navigate to `Administration > Users` page on the web.
- Click on the user edit button.
- Set `/mnt/media` to be the external path. This folder will only contain the three folders that we want to import, so nothing else can be accessed.
:::note
Spaces in the internal path aren't currently supported.
You must import it as:
`..:/mnt/media/my-media:ro`
instead of
`..:/mnt/media/my media:ro`
:::
### Create External Libraries

View File

@@ -1,6 +1,6 @@
import MobileAppDownload from '../partials/_mobile-app-download.md';
import MobileAppLogin from '../partials/_mobile-app-login.md';
import MobileAppBackup from '../partials/_mobile-app-backup.md';
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
# Mobile App

View File

@@ -6,6 +6,8 @@ Smart search is powered by the [pgvecto.rs](https://github.com/tensorchord/pgvec
Metadata search (prefixed with `m:`) can search specifically by text without the use of a model.
Archived photos are not included in search results by default. To include them, add the query parameter `withArchived=true` to the url.
Some search examples:
<img src={require('./img/search-ex-2.webp').default} title='Search Example 1' />

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,40 @@
# Database GUI
A short guide on connecting [pgAdmin](https://www.pgadmin.org/) to Immich.
:::note
- In order to connect to the database the immich_postgres container **must be running**.
- The passwords and usernames used below match the ones specified in the example `.env` file. If changed, please use actual values instead.
:::
## 1. Install pgAdmin
Download and install [pgAdmin](https://www.pgadmin.org/download/) following the official documentation.
## 2. Add a Server
Open pgAdmin and click "Add New Server".
<img src={require('./img/add-new-server-option.png').default} width="50%" title="new server option" />
## 3. Enter Connection Details
| Name | Value |
| -------------------- | ----------- |
| Host name/address | `localhost` |
| Port | `5432` |
| Maintenance database | `immich` |
| Username | `postgres` |
| Password | `postgres` |
<img src={require('./img/Connection-Pgadmin.png').default} width="75%" title="Connection" />
## 4. Save Connection
Click on "Save" to connect to the Immich database.
:::tip
View [Database Queries](https://immich.app/docs/guides/database-queries/) for common database queries.
:::

View File

@@ -1,6 +1,6 @@
# External Library
This guide walks you through adding an [External Library](../features/libraries#external-libraries).
This guide walks you through adding an [External Library](/docs/features/libraries#external-libraries).
This guide assumes you are running Immich in Docker and that the files you wish to access are stored
in a directory on the same machine.
@@ -78,7 +78,7 @@ In the Immich web UI:
- Click \*_Add path_
<img src={require('./img/add-path-button.png').default} width="50%" title="Add Path button" />
- Enter **.** as the path and click Add
- Enter **/usr/src/app/external** as the path and click Add
<img src={require('./img/add-path-field.png').default} width="50%" title="Add Path field" />
- Save the new path

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@@ -1,6 +1,7 @@
# Remote Access
This page gives a few pointers on how to access your Immich instance from outside your LAN.
You can read the [full discussion in Discord](https://discord.com/channels/979116623879368755/1122615710846308484)
:::danger
Never forward port 2283 directly to the internet without additional configuration. This will expose the web interface via http to the internet, making you succeptible to [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks.

View File

@@ -1,6 +1,6 @@
# Remote Machine Learning
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):
To alleviate [performance issues on low-memory systems](/docs/FAQ.mdx#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 the URL in Machine Learning Settings on the Admin Settings page to point to the designated ML system, e.g. `http://workstation:3003`.
- Copy the following `docker-compose.yml` to your ML system.

View File

@@ -0,0 +1,38 @@
# Remove Offline Files
:::note
**Before running the script**, please make sure you have a [backup](/docs/administration/backup-and-restore) of your assets and database
:::
This page is a guide to get rid of offline files from the repair page.
This way works by downloading a JSON file that contains a list of all the files that are defined as offline files, running a script that uses the [Immich API](/docs/api/delete-assets) in order to remove the offline files.
1. Create an API key under Admin User -> Account Settings -> API Keys -> New API Key -> Copy to clipboard.
2. Download the JSON file under Administration -> repair -> Export.
3. Replace `YOUR_IP_HERE` and `YOUR_API_KEY_HERE` with your actual IP address and API key in the script.
4. Run the script in the same folder where the JSON file is located.
## Script for Linux based systems:
```
awk -F\" '/entityId/ {print $4}' orphans.json | while read line; do curl --location --request DELETE 'http://YOUR_IP_HERE:2283/api/asset' --header 'Content- Type: application/json' --header 'x-api-key: YOUR_API_KEY_HERE' --data '{ "force": true, "ids": ["'"$line"'"]}';done
```
## Script for the Windows system (run through PowerShell):
```
Get-Content orphans.json | Select-String -Pattern 'entityId' | ForEach-Object {
$line = $_ -split '"' | Select-Object -Index 3
$body = [pscustomobject]@{
'ids' = @($line)
'force' = (' true ' | ConvertFrom-Json)
} | ConvertTo-Json -Depth 3
Invoke-RestMethod -Uri 'http://YOUR_IP_HERE:2283/api/asset' -Method Delete -Headers @{
'Content-Type' = 'application/json'
'x-api-key' = 'YOUR_API_KEY_HERE'
} -Body $body
}
```
Thanks to [DooMRunneR](https://discord.com/channels/979116623879368755/1179655214870040596/1194308198413373482) for writing this script.

View File

@@ -79,7 +79,7 @@ The default configuration looks like this:
"modelName": "buffalo_l",
"minScore": 0.7,
"maxDistance": 0.6,
"minFaces": 1
"minFaces": 3
}
},
"map": {

View File

@@ -121,16 +121,16 @@ Redis (Sentinel) URL example JSON before encoding:
## Machine Learning
| Variable | Description | Default | Services |
| :----------------------------------------------- | :---------------------------------------------------------------- | :-----------------: | :--------------- |
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if <= 0) | `300` | machine learning |
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if <= 0) | `10` | machine learning |
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if <= 0) | number of CPU cores | machine learning |
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
| Variable | Description | Default | Services |
| :----------------------------------------------- | :----------------------------------------------------------------- | :-----------------: | :--------------- |
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` | machine learning |
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.

View File

@@ -2,12 +2,12 @@
sidebar_position: 90
---
import RegisterAdminUser from '../partials/_register-admin.md';
import UserCreate from '../partials/_user-create.md';
import StorageTemplate from '../partials/_storage-template.md';
import MobileAppDownload from '../partials/_mobile-app-download.md';
import MobileAppLogin from '../partials/_mobile-app-login.md';
import MobileAppBackup from '../partials/_mobile-app-backup.md';
import RegisterAdminUser from '/docs/partials/_register-admin.md';
import UserCreate from '/docs/partials/_user-create.md';
import StorageTemplate from '/docs/partials/_storage-template.md';
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
# Post Install Steps

View File

@@ -0,0 +1,12 @@
---
sidebar_position: 2
---
# Comparison
If you're new here and came from other photo self-hosting alternatives you might want to look at a comparison between Immich and your current self-hosting.
Here you can see a [comparison between the various OpenSource Photo Libraries](https://meichthys.github.io/foss_photo_libraries/) including Immich.
:::note
It is important to remember, Immich is under very active development. Expect bugs and changes. Do not use it as the only way to store your photos and videos!
:::

View File

@@ -1,12 +1,12 @@
---
sidebar_position: 5
sidebar_position: 6
---
# Help Me!
Running into an issue or have a question? Try the following:
1. Check the [FAQs](/docs/FAQ.md).
1. Check the [FAQs](/docs/FAQ.mdx).
2. Read through the [Release Notes][github-releases].
3. Search through existing [GitHub Issues][github-issues].
4. Open a help ticket on [Discord][discord-link].

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 3
sidebar_position: 4
---
# Logo

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
---
# Quick Start
@@ -10,11 +10,11 @@ to install and use it.
## Requirements
Check the [requirements page](../install/requirements) to get started.
Check the [requirements page](/docs/install/requirements) to get started.
## Install and launch via Docker Compose
Follow the [Docker Compose (Recommended)](../install/docker-compose) instructions
Follow the [Docker Compose (Recommended)](/docs/install/docker-compose) instructions
to install the server.
- Where random passwords are required, `pwgen` is a handy utility.
@@ -24,7 +24,7 @@ to install the server.
## Try the Web UI
import RegisterAdminUser from '../partials/_register-admin.md';
import RegisterAdminUser from '/docs/partials/_register-admin.md';
<RegisterAdminUser />
@@ -36,13 +36,13 @@ Try uploading a picture from your browser.
### Download the Mobile App
import MobileAppDownload from '../partials/_mobile-app-download.md';
import MobileAppDownload from '/docs/partials/_mobile-app-download.md';
<MobileAppDownload />
### Login to the Mobile App
import MobileAppLogin from '../partials/_mobile-app-login.md';
import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
<MobileAppLogin />
@@ -50,7 +50,7 @@ In the mobile app, you should see the photo you uploaded from the web UI.
### Transfer Photos from your Mobile Device
import MobileAppBackup from '../partials/_mobile-app-backup.md';
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
<MobileAppBackup />
@@ -59,13 +59,13 @@ take quite a while.
You can select the Jobs tab to see Immich processing your photos.
<img src={require('../guides/img/jobs-tab.png').default} title="Jobs tab" />
<img src={require('/docs/guides/img/jobs-tab.png').default} title="Jobs tab" />
## Set up your backups
You may want to back up the content of your Immich instance
along with other parts of your server; be sure to read about
[database backup](../administration/backup-and-restore).
[database backup](/docs/administration/backup-and-restore).
## Where to go from here?
@@ -77,11 +77,11 @@ even those not on your mobile device, via Google Takeout.
You can use [immich-go](https://github.com/simulot/immich-go) for this.
You may want to
[upload photos from your own archive](../features/command-line-interface).
[upload photos from your own archive](/docs/features/command-line-interface).
You may want to incorporate an immutable archive of photos from an
[External Library](../features/libraries#external-libraries);
there's a [Guide](../guides/external-library) for that.
[External Library](/docs/features/libraries#external-libraries);
there's a [Guide](/docs/guides/external-library) for that.
You may want your mobile device to
[back photos up to your server automatically](../features/automatic-backup).
[back photos up to your server automatically](/docs/features/automatic-backup).

View File

@@ -1,5 +1,5 @@
---
sidebar_position: 4
sidebar_position: 5
---
# Support The Project

View File

@@ -1,8 +1,7 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
const prism = require('prism-react-renderer');
/** @type {import('@docusaurus/types').Config} */
const config = {
@@ -56,7 +55,7 @@ const config = {
editUrl: 'https://github.com/immich-app/immich/tree/main/docs/',
},
api: {
path: '../server/immich-openapi-specs.json',
path: '../open-api/immich-openapi-specs.json',
routeBasePath: '/docs/api',
},
// blog: {
@@ -165,8 +164,8 @@ const config = {
copyright: `Immich is available as open source under the terms of the MIT License.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
theme: prism.themes.github,
darkTheme: prism.themes.dracula,
additionalLanguages: ['sql'],
},
image: 'overview/img/feature-panel.png',

19471
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,32 +13,31 @@
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"check": "tsc"
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^2.4.3",
"@docusaurus/preset-classic": "^2.4.3",
"@docusaurus/core": "^3.1.0",
"@docusaurus/preset-classic": "^3.1.0",
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^1.6.22",
"autoprefixer": "^10.4.13",
"@mdx-js/react": "^3.0.0",
"autoprefixer": "^10.4.17",
"classnames": "^2.3.2",
"clsx": "^2.0.0",
"docusaurus-lunr-search": "^2.3.2",
"docusaurus-preset-openapi": "^0.6.3",
"docusaurus-lunr-search": "^3.3.2",
"docusaurus-preset-openapi": "^0.7.3",
"postcss": "^8.4.25",
"prism-react-renderer": "^1.3.5",
"prism-react-renderer": "^2.3.1",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tailwindcss": "^3.2.4",
"url": "^0.11.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^2.4.1",
"@tsconfig/docusaurus": "^1.0.5",
"prettier": "^3.0.0",
"@docusaurus/module-type-aliases": "^3.1.0",
"@tsconfig/docusaurus": "^2.0.2",
"prettier": "^3.2.4",
"typescript": "^5.1.6"
},
"browserslist": {

View File

@@ -4,6 +4,6 @@
"compilerOptions": {
"baseUrl": ".",
"module": "Node16"
}
"module": "Node16",
},
}

View File

@@ -1,4 +1,4 @@
FROM python:3.11-bookworm@sha256:291405e32318285d8913b7b03293777c255fb1e89305c82aa495ac747b0049fe as builder
FROM python:3.11-bookworm@sha256:497c00ec2cff14316a6859c4e30fc88e7ab1f11dd254fb43b8f4b201ca657596 as builder
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -13,17 +13,40 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
FROM python:3.11-slim-bookworm@sha256:8f64a67710f3d981cf3008d6f9f1dbe61accd7927f165f4e37ea3f8b883ccc3f
ARG TARGETPLATFORM
ENV ARMNN_PATH=/opt/armnn
COPY ann /opt/ann
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
mkdir /opt/armnn && \
curl -SL "https://github.com/ARM-software/armnn/releases/download/v23.11/ArmNN-linux-aarch64.tar.gz" | tar -zx -C /opt/armnn && \
cd /opt/ann && \
sh build.sh; \
else \
mkdir /opt/armnn; \
fi
FROM python:3.11-slim-bookworm@sha256:637774748f62b832dc11e7b286e48cd716727ed04b45a0322776c01bc526afc3
ARG TARGETPLATFORM
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
apt-get update && apt-get install -y --no-install-recommends ocl-icd-libopencl1 mesa-opencl-icd && \
rm -rf /var/lib/apt/lists/* && \
mkdir --parents /etc/OpenCL/vendors && \
echo "/usr/lib/libmali.so" > /etc/OpenCL/vendors/mali.icd && \
mkdir /opt/armnn; \
fi
WORKDIR /usr/src/app
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \
PYTHONPATH=/usr/src
PYTHONPATH=/usr/src \
LD_LIBRARY_PATH=/opt/armnn
# prevent core dumps
RUN echo "hard core 0" >> /etc/security/limits.conf && \
@@ -31,7 +54,10 @@ RUN echo "hard core 0" >> /etc/security/limits.conf && \
echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /opt/armnn/libarmnn.so.?? /opt/armnn/libarmnnOnnxParser.so.?? /opt/armnn/libarmnnDeserializer.so.?? /opt/armnn/libarmnnTfLiteParser.so.?? /opt/armnn/libprotobuf.so.?.??.?.? /opt/ann/libann.s[o] /opt/ann/build.sh /opt/armnn
COPY ann/ann.py /usr/src/ann/ann.py
COPY start.sh log_conf.json ./
COPY app .
ENTRYPOINT ["tini", "--"]
CMD ["./start.sh"]

View File

@@ -0,0 +1 @@
from .ann import Ann, is_available

View File

@@ -0,0 +1,281 @@
#include <fstream>
#include <mutex>
#include <atomic>
#include "armnn/IRuntime.hpp"
#include "armnn/INetwork.hpp"
#include "armnn/Types.hpp"
#include "armnnDeserializer/IDeserializer.hpp"
#include "armnnTfLiteParser/ITfLiteParser.hpp"
#include "armnnOnnxParser/IOnnxParser.hpp"
using namespace armnn;
struct IOInfos
{
std::vector<BindingPointInfo> inputInfos;
std::vector<BindingPointInfo> outputInfos;
};
// from https://rigtorp.se/spinlock/
struct SpinLock
{
std::atomic<bool> lock_ = {false};
void lock()
{
for (;;)
{
if (!lock_.exchange(true, std::memory_order_acquire))
{
break;
}
while (lock_.load(std::memory_order_relaxed))
;
}
}
void unlock() { lock_.store(false, std::memory_order_release); }
};
class Ann
{
public:
int load(const char *modelPath,
bool fastMath,
bool fp16,
bool saveCachedNetwork,
const char *cachedNetworkPath)
{
INetworkPtr network = loadModel(modelPath);
IOptimizedNetworkPtr optNet = OptimizeNetwork(network.get(), fastMath, fp16, saveCachedNetwork, cachedNetworkPath);
const IOInfos infos = getIOInfos(optNet.get());
NetworkId netId;
mutex.lock();
Status status = runtime->LoadNetwork(netId, std::move(optNet));
mutex.unlock();
if (status != Status::Success)
{
return -1;
}
spinLock.lock();
ioInfos[netId] = infos;
mutexes.emplace(netId, std::make_unique<std::mutex>());
spinLock.unlock();
return netId;
}
void execute(NetworkId netId, const void **inputData, void **outputData)
{
spinLock.lock();
const IOInfos *infos = &ioInfos[netId];
auto m = mutexes[netId].get();
spinLock.unlock();
InputTensors inputTensors;
inputTensors.reserve(infos->inputInfos.size());
size_t i = 0;
for (const BindingPointInfo &info : infos->inputInfos)
inputTensors.emplace_back(info.first, ConstTensor(info.second, inputData[i++]));
OutputTensors outputTensors;
outputTensors.reserve(infos->outputInfos.size());
i = 0;
for (const BindingPointInfo &info : infos->outputInfos)
outputTensors.emplace_back(info.first, Tensor(info.second, outputData[i++]));
m->lock();
runtime->EnqueueWorkload(netId, inputTensors, outputTensors);
m->unlock();
}
void unload(NetworkId netId)
{
mutex.lock();
runtime->UnloadNetwork(netId);
mutex.unlock();
}
int tensors(NetworkId netId, bool isInput = false)
{
spinLock.lock();
const IOInfos *infos = &ioInfos[netId];
spinLock.unlock();
return (int)(isInput ? infos->inputInfos.size() : infos->outputInfos.size());
}
unsigned long shape(NetworkId netId, bool isInput = false, int index = 0)
{
spinLock.lock();
const IOInfos *infos = &ioInfos[netId];
spinLock.unlock();
const TensorShape shape = (isInput ? infos->inputInfos : infos->outputInfos)[index].second.GetShape();
unsigned long s = 0;
for (unsigned int d = 0; d < shape.GetNumDimensions(); d++)
s |= ((unsigned long)shape[d]) << (d * 16); // stores up to 4 16-bit values in a 64-bit value
return s;
}
Ann(int tuningLevel, const char *tuningFile)
{
IRuntime::CreationOptions runtimeOptions;
BackendOptions backendOptions{"GpuAcc",
{
{"TuningLevel", tuningLevel},
{"MemoryOptimizerStrategy", "ConstantMemoryStrategy"}, // SingleAxisPriorityList or ConstantMemoryStrategy
}};
if (tuningFile)
backendOptions.AddOption({"TuningFile", tuningFile});
runtimeOptions.m_BackendOptions.emplace_back(backendOptions);
runtime = IRuntime::CreateRaw(runtimeOptions);
};
~Ann()
{
IRuntime::Destroy(runtime);
};
private:
INetworkPtr loadModel(const char *modelPath)
{
const auto path = std::string(modelPath);
if (path.rfind(".tflite") == path.length() - 7) // endsWith()
{
auto parser = armnnTfLiteParser::ITfLiteParser::CreateRaw();
return parser->CreateNetworkFromBinaryFile(modelPath);
}
else if (path.rfind(".onnx") == path.length() - 5) // endsWith()
{
auto parser = armnnOnnxParser::IOnnxParser::CreateRaw();
return parser->CreateNetworkFromBinaryFile(modelPath);
}
else
{
std::ifstream ifs(path, std::ifstream::in | std::ifstream::binary);
auto parser = armnnDeserializer::IDeserializer::CreateRaw();
return parser->CreateNetworkFromBinary(ifs);
}
}
static BindingPointInfo getInputTensorInfo(LayerBindingId inputBindingId, TensorInfo info)
{
const auto newInfo = TensorInfo{info.GetShape(), info.GetDataType(),
info.GetQuantizationScale(),
info.GetQuantizationOffset(),
true};
return {inputBindingId, newInfo};
}
IOptimizedNetworkPtr OptimizeNetwork(INetwork *network, bool fastMath, bool fp16, bool saveCachedNetwork, const char *cachedNetworkPath)
{
const bool allowExpandedDims = false;
const ShapeInferenceMethod shapeInferenceMethod = ShapeInferenceMethod::ValidateOnly;
OptimizerOptionsOpaque options;
options.SetReduceFp32ToFp16(fp16);
options.SetShapeInferenceMethod(shapeInferenceMethod);
options.SetAllowExpandedDims(allowExpandedDims);
BackendOptions gpuAcc("GpuAcc", {{"FastMathEnabled", fastMath}});
if (cachedNetworkPath)
{
gpuAcc.AddOption({"SaveCachedNetwork", saveCachedNetwork});
gpuAcc.AddOption({"CachedNetworkFilePath", cachedNetworkPath});
}
options.AddModelOption(gpuAcc);
// No point in using ARMNN for CPU, use ONNX (quantized) instead.
// BackendOptions cpuAcc("CpuAcc",
// {
// {"FastMathEnabled", fastMath},
// {"NumberOfThreads", 0},
// });
// options.AddModelOption(cpuAcc);
BackendOptions allowExDimOpt("AllowExpandedDims",
{{"AllowExpandedDims", allowExpandedDims}});
options.AddModelOption(allowExDimOpt);
BackendOptions shapeInferOpt("ShapeInferenceMethod",
{{"InferAndValidate", shapeInferenceMethod == ShapeInferenceMethod::InferAndValidate}});
options.AddModelOption(shapeInferOpt);
std::vector<BackendId> backends = {
BackendId("GpuAcc"),
// BackendId("CpuAcc"),
// BackendId("CpuRef"),
};
return Optimize(*network, backends, runtime->GetDeviceSpec(), options);
}
IOInfos getIOInfos(IOptimizedNetwork *optNet)
{
struct InfoStrategy : IStrategy
{
void ExecuteStrategy(const IConnectableLayer *layer,
const BaseDescriptor &descriptor,
const std::vector<ConstTensor> &constants,
const char *name,
const LayerBindingId id = 0) override
{
IgnoreUnused(descriptor, constants, id);
const LayerType lt = layer->GetType();
if (lt == LayerType::Input)
ioInfos.inputInfos.push_back(getInputTensorInfo(id, layer->GetOutputSlot(0).GetTensorInfo()));
else if (lt == LayerType::Output)
ioInfos.outputInfos.push_back({id, layer->GetInputSlot(0).GetTensorInfo()});
}
IOInfos ioInfos;
};
InfoStrategy infoStrategy;
optNet->ExecuteStrategy(infoStrategy);
return infoStrategy.ioInfos;
}
IRuntime *runtime;
std::map<NetworkId, IOInfos> ioInfos;
std::map<NetworkId, std::unique_ptr<std::mutex>> mutexes; // mutex per network to not execute the same the same network concurrently
std::mutex mutex; // global mutex for load/unload calls to the runtime
SpinLock spinLock; // fast spin lock to guard access to the ioInfos and mutexes maps
};
extern "C" void *init(int logLevel, int tuningLevel, const char *tuningFile)
{
LogSeverity level = static_cast<LogSeverity>(logLevel);
ConfigureLogging(true, true, level);
Ann *ann = new Ann(tuningLevel, tuningFile);
return ann;
}
extern "C" void destroy(void *ann)
{
delete ((Ann *)ann);
}
extern "C" int load(void *ann,
const char *path,
bool fastMath,
bool fp16,
bool saveCachedNetwork,
const char *cachedNetworkPath)
{
return ((Ann *)ann)->load(path, fastMath, fp16, saveCachedNetwork, cachedNetworkPath);
}
extern "C" void unload(void *ann, NetworkId netId)
{
((Ann *)ann)->unload(netId);
}
extern "C" void execute(void *ann, NetworkId netId, const void **inputData, void **outputData)
{
((Ann *)ann)->execute(netId, inputData, outputData);
}
extern "C" unsigned long shape(void *ann, NetworkId netId, bool isInput, int index)
{
return ((Ann *)ann)->shape(netId, isInput, index);
}
extern "C" int tensors(void *ann, NetworkId netId, bool isInput)
{
return ((Ann *)ann)->tensors(netId, isInput);
}

162
machine-learning/ann/ann.py Normal file
View File

@@ -0,0 +1,162 @@
from __future__ import annotations
from ctypes import CDLL, Array, c_bool, c_char_p, c_int, c_ulong, c_void_p
from os.path import exists
from typing import Any, Protocol, TypeVar
import numpy as np
from numpy.typing import NDArray
from app.config import log
try:
CDLL("libmali.so") # fail if libmali.so is not mounted into container
libann = CDLL("libann.so")
libann.init.argtypes = c_int, c_int, c_char_p
libann.init.restype = c_void_p
libann.load.argtypes = c_void_p, c_char_p, c_bool, c_bool, c_bool, c_char_p
libann.load.restype = c_int
libann.execute.argtypes = c_void_p, c_int, Array[c_void_p], Array[c_void_p]
libann.unload.argtypes = c_void_p, c_int
libann.destroy.argtypes = (c_void_p,)
libann.shape.argtypes = c_void_p, c_int, c_bool, c_int
libann.shape.restype = c_ulong
libann.tensors.argtypes = c_void_p, c_int, c_bool
libann.tensors.restype = c_int
is_available = True
except OSError as e:
log.debug("Could not load ANN shared libraries, using ONNX: %s", e)
is_available = False
T = TypeVar("T", covariant=True)
class Newable(Protocol[T]):
def new(self) -> None:
...
class _Singleton(type, Newable[T]):
_instances: dict[_Singleton[T], Newable[T]] = {}
def __call__(cls, *args: Any, **kwargs: Any) -> Newable[T]:
if cls not in cls._instances:
obj: Newable[T] = super(_Singleton, cls).__call__(*args, **kwargs)
cls._instances[cls] = obj
else:
obj = cls._instances[cls]
obj.new()
return obj
class Ann(metaclass=_Singleton):
def __init__(self, log_level: int = 3, tuning_level: int = 1, tuning_file: str | None = None) -> None:
if not is_available:
raise RuntimeError("libann is not available!")
if tuning_file and not exists(tuning_file):
raise ValueError("tuning_file must point to an existing (possibly empty) file!")
if tuning_level == 0 and tuning_file is None:
raise ValueError("tuning_level == 0 reads existing tuning information and requires a tuning_file")
if tuning_level < 0 or tuning_level > 3:
raise ValueError("tuning_level must be 0 (load from tuning_file), 1, 2 or 3.")
if log_level < 0 or log_level > 5:
raise ValueError("log_level must be 0 (trace), 1 (debug), 2 (info), 3 (warning), 4 (error) or 5 (fatal)")
self.log_level = log_level
self.tuning_level = tuning_level
self.tuning_file = tuning_file
self.output_shapes: dict[int, tuple[tuple[int], ...]] = {}
self.input_shapes: dict[int, tuple[tuple[int], ...]] = {}
self.ann: int | None = None
self.new()
def new(self) -> None:
if self.ann is None:
self.ann = libann.init(
self.log_level,
self.tuning_level,
self.tuning_file.encode() if self.tuning_file is not None else None,
)
self.ref_count = 0
self.ref_count += 1
def destroy(self) -> None:
self.ref_count -= 1
if self.ref_count <= 0 and self.ann is not None:
libann.destroy(self.ann)
self.ann = None
def __del__(self) -> None:
if self.ann is not None:
libann.destroy(self.ann)
self.ann = None
def load(
self,
model_path: str,
fast_math: bool = True,
fp16: bool = False,
save_cached_network: bool = False,
cached_network_path: str | None = None,
) -> int:
if not model_path.endswith((".armnn", ".tflite", ".onnx")):
raise ValueError("model_path must be a file with extension .armnn, .tflite or .onnx")
if not exists(model_path):
raise ValueError("model_path must point to an existing file!")
if cached_network_path is not None and not exists(cached_network_path):
raise ValueError("cached_network_path must point to an existing (possibly empty) file!")
if save_cached_network and cached_network_path is None:
raise ValueError("save_cached_network is True, cached_network_path must be specified!")
net_id: int = libann.load(
self.ann,
model_path.encode(),
fast_math,
fp16,
save_cached_network,
cached_network_path.encode() if cached_network_path is not None else None,
)
self.input_shapes[net_id] = tuple(
self.shape(net_id, input=True, index=i) for i in range(self.tensors(net_id, input=True))
)
self.output_shapes[net_id] = tuple(
self.shape(net_id, input=False, index=i) for i in range(self.tensors(net_id, input=False))
)
return net_id
def unload(self, network_id: int) -> None:
libann.unload(self.ann, network_id)
del self.output_shapes[network_id]
def execute(self, network_id: int, input_tensors: list[NDArray[np.float32]]) -> list[NDArray[np.float32]]:
if not isinstance(input_tensors, list):
raise ValueError("input_tensors needs to be a list!")
net_input_shapes = self.input_shapes[network_id]
if len(input_tensors) != len(net_input_shapes):
raise ValueError(f"input_tensors lengths {len(input_tensors)} != network inputs {len(net_input_shapes)}")
for net_input_shape, input_tensor in zip(net_input_shapes, input_tensors):
if net_input_shape != input_tensor.shape:
raise ValueError(f"input_tensor shape {input_tensor.shape} != network input shape {net_input_shape}")
if not input_tensor.flags.c_contiguous:
raise ValueError("input_tensors must be c_contiguous numpy ndarrays")
output_tensors: list[NDArray[np.float32]] = [
np.ndarray(s, dtype=np.float32) for s in self.output_shapes[network_id]
]
input_type = c_void_p * len(input_tensors)
inputs = input_type(*[t.ctypes.data_as(c_void_p) for t in input_tensors])
output_type = c_void_p * len(output_tensors)
outputs = output_type(*[t.ctypes.data_as(c_void_p) for t in output_tensors])
libann.execute(self.ann, network_id, inputs, outputs)
return output_tensors
def shape(self, network_id: int, input: bool = False, index: int = 0) -> tuple[int]:
s = libann.shape(self.ann, network_id, input, index)
a = []
while s != 0:
a.append(s & 0xFFFF)
s >>= 16
return tuple(a)
def tensors(self, network_id: int, input: bool = False) -> int:
tensors: int = libann.tensors(self.ann, network_id, input)
return tensors

View File

@@ -0,0 +1 @@
g++ -shared -O3 -o libann.so -fuse-ld=gold -std=c++17 -I$ARMNN_PATH/include -larmnn -larmnnDeserializer -larmnnTfLiteParser -larmnnOnnxParser -L$ARMNN_PATH ann.cpp

View File

@@ -0,0 +1,2 @@
armnn*
output/

View File

@@ -0,0 +1,4 @@
#!/bin/sh
cd armnn-23.11/
g++ -o ../armnnconverter -O1 -DARMNN_ONNX_PARSER -DARMNN_SERIALIZER -DARMNN_TF_LITE_PARSER -fuse-ld=gold -std=c++17 -Iinclude -Isrc/armnnUtils -Ithird-party -larmnn -larmnnDeserializer -larmnnTfLiteParser -larmnnOnnxParser -larmnnSerializer -L../armnn src/armnnConverter/ArmnnConverter.cpp

View File

@@ -0,0 +1,8 @@
#!/bin/sh
# binaries
mkdir armnn
curl -SL "https://github.com/ARM-software/armnn/releases/download/v23.11/ArmNN-linux-x86_64.tar.gz" | tar -zx -C armnn
# source to build ArmnnConverter
curl -SL "https://github.com/ARM-software/armnn/archive/refs/tags/v23.11.tar.gz" | tar -zx

View File

@@ -0,0 +1,201 @@
name: annexport
channels:
- pytorch
- nvidia
- conda-forge
dependencies:
- _libgcc_mutex=0.1=conda_forge
- _openmp_mutex=4.5=2_kmp_llvm
- aiohttp=3.9.1=py310h2372a71_0
- aiosignal=1.3.1=pyhd8ed1ab_0
- arpack=3.8.0=nompi_h0baa96a_101
- async-timeout=4.0.3=pyhd8ed1ab_0
- attrs=23.1.0=pyh71513ae_1
- aws-c-auth=0.7.3=h28f7589_1
- aws-c-cal=0.6.1=hc309b26_1
- aws-c-common=0.9.0=hd590300_0
- aws-c-compression=0.2.17=h4d4d85c_2
- aws-c-event-stream=0.3.1=h2e3709c_4
- aws-c-http=0.7.11=h00aa349_4
- aws-c-io=0.13.32=he9a53bd_1
- aws-c-mqtt=0.9.3=hb447be9_1
- aws-c-s3=0.3.14=hf3aad02_1
- aws-c-sdkutils=0.1.12=h4d4d85c_1
- aws-checksums=0.1.17=h4d4d85c_1
- aws-crt-cpp=0.21.0=hb942446_5
- aws-sdk-cpp=1.10.57=h85b1a90_19
- blas=2.120=openblas
- blas-devel=3.9.0=20_linux64_openblas
- brotli-python=1.0.9=py310hd8f1fbe_9
- bzip2=1.0.8=hd590300_5
- c-ares=1.23.0=hd590300_0
- ca-certificates=2023.11.17=hbcca054_0
- certifi=2023.11.17=pyhd8ed1ab_0
- charset-normalizer=3.3.2=pyhd8ed1ab_0
- click=8.1.7=unix_pyh707e725_0
- colorama=0.4.6=pyhd8ed1ab_0
- coloredlogs=15.0.1=pyhd8ed1ab_3
- cuda-cudart=11.7.99=0
- cuda-cupti=11.7.101=0
- cuda-libraries=11.7.1=0
- cuda-nvrtc=11.7.99=0
- cuda-nvtx=11.7.91=0
- cuda-runtime=11.7.1=0
- dataclasses=0.8=pyhc8e2a94_3
- datasets=2.14.7=pyhd8ed1ab_0
- dill=0.3.7=pyhd8ed1ab_0
- filelock=3.13.1=pyhd8ed1ab_0
- flatbuffers=23.5.26=h59595ed_1
- freetype=2.12.1=h267a509_2
- frozenlist=1.4.0=py310h2372a71_1
- fsspec=2023.10.0=pyhca7485f_0
- ftfy=6.1.3=pyhd8ed1ab_0
- gflags=2.2.2=he1b5a44_1004
- glog=0.6.0=h6f12383_0
- glpk=5.0=h445213a_0
- gmp=6.3.0=h59595ed_0
- gmpy2=2.1.2=py310h3ec546c_1
- huggingface_hub=0.17.3=pyhd8ed1ab_0
- humanfriendly=10.0=pyhd8ed1ab_6
- icu=73.2=h59595ed_0
- idna=3.6=pyhd8ed1ab_0
- importlib-metadata=7.0.0=pyha770c72_0
- importlib_metadata=7.0.0=hd8ed1ab_0
- joblib=1.3.2=pyhd8ed1ab_0
- keyutils=1.6.1=h166bdaf_0
- krb5=1.21.2=h659d440_0
- lcms2=2.15=h7f713cb_2
- ld_impl_linux-64=2.40=h41732ed_0
- lerc=4.0.0=h27087fc_0
- libabseil=20230125.3=cxx17_h59595ed_0
- libarrow=12.0.1=hb87d912_8_cpu
- libblas=3.9.0=20_linux64_openblas
- libbrotlicommon=1.0.9=h166bdaf_9
- libbrotlidec=1.0.9=h166bdaf_9
- libbrotlienc=1.0.9=h166bdaf_9
- libcblas=3.9.0=20_linux64_openblas
- libcrc32c=1.1.2=h9c3ff4c_0
- libcublas=11.10.3.66=0
- libcufft=10.7.2.124=h4fbf590_0
- libcufile=1.8.1.2=0
- libcurand=10.3.4.101=0
- libcurl=8.5.0=hca28451_0
- libcusolver=11.4.0.1=0
- libcusparse=11.7.4.91=0
- libdeflate=1.19=hd590300_0
- libedit=3.1.20191231=he28a2e2_2
- libev=4.33=hd590300_2
- libevent=2.1.12=hf998b51_1
- libffi=3.4.2=h7f98852_5
- libgcc-ng=13.2.0=h807b86a_3
- libgfortran-ng=13.2.0=h69a702a_3
- libgfortran5=13.2.0=ha4646dd_3
- libgoogle-cloud=2.12.0=hac9eb74_1
- libgrpc=1.54.3=hb20ce57_0
- libhwloc=2.9.3=default_h554bfaf_1009
- libiconv=1.17=hd590300_1
- libjpeg-turbo=2.1.5.1=hd590300_1
- liblapack=3.9.0=20_linux64_openblas
- liblapacke=3.9.0=20_linux64_openblas
- libnghttp2=1.58.0=h47da74e_1
- libnpp=11.7.4.75=0
- libnsl=2.0.1=hd590300_0
- libnuma=2.0.16=h0b41bf4_1
- libnvjpeg=11.8.0.2=0
- libopenblas=0.3.25=pthreads_h413a1c8_0
- libpng=1.6.39=h753d276_0
- libprotobuf=3.21.12=hfc55251_2
- libsentencepiece=0.1.99=h180e1df_0
- libsqlite=3.44.2=h2797004_0
- libssh2=1.11.0=h0841786_0
- libstdcxx-ng=13.2.0=h7e041cc_3
- libthrift=0.18.1=h8fd135c_2
- libtiff=4.6.0=h29866fb_1
- libutf8proc=2.8.0=h166bdaf_0
- libuuid=2.38.1=h0b41bf4_0
- libwebp-base=1.3.2=hd590300_0
- libxcb=1.15=h0b41bf4_0
- libxml2=2.11.6=h232c23b_0
- libzlib=1.2.13=hd590300_5
- llvm-openmp=17.0.6=h4dfa4b3_0
- lz4-c=1.9.4=hcb278e6_0
- mkl=2022.2.1=h84fe81f_16997
- mkl-devel=2022.2.1=ha770c72_16998
- mkl-include=2022.2.1=h84fe81f_16997
- mpc=1.3.1=hfe3b2da_0
- mpfr=4.2.1=h9458935_0
- mpmath=1.3.0=pyhd8ed1ab_0
- multidict=6.0.4=py310h2372a71_1
- multiprocess=0.70.15=py310h2372a71_1
- ncurses=6.4=h59595ed_2
- numpy=1.26.2=py310hb13e2d6_0
- onnx=1.14.0=py310ha3deec4_1
- onnx2torch=1.5.13=pyhd8ed1ab_0
- onnxruntime=1.16.3=py310hd4b7fbc_1_cpu
- open-clip-torch=2.23.0=pyhd8ed1ab_1
- openblas=0.3.25=pthreads_h7a3da1a_0
- openjpeg=2.5.0=h488ebb8_3
- openssl=3.2.0=hd590300_1
- orc=1.9.0=h2f23424_1
- packaging=23.2=pyhd8ed1ab_0
- pandas=2.1.4=py310hcc13569_0
- pillow=10.0.1=py310h29da1c1_1
- pip=23.3.1=pyhd8ed1ab_0
- protobuf=4.21.12=py310heca2aa9_0
- pthread-stubs=0.4=h36c2ea0_1001
- pyarrow=12.0.1=py310h0576679_8_cpu
- pyarrow-hotfix=0.6=pyhd8ed1ab_0
- pysocks=1.7.1=pyha2e5f31_6
- python=3.10.13=hd12c33a_0_cpython
- python-dateutil=2.8.2=pyhd8ed1ab_0
- python-flatbuffers=23.5.26=pyhd8ed1ab_0
- python-tzdata=2023.3=pyhd8ed1ab_0
- python-xxhash=3.4.1=py310h2372a71_0
- python_abi=3.10=4_cp310
- pytorch=1.13.1=cpu_py310hd11e9c7_1
- pytorch-cuda=11.7=h778d358_5
- pytorch-mutex=1.0=cuda
- pytz=2023.3.post1=pyhd8ed1ab_0
- pyyaml=6.0.1=py310h2372a71_1
- rdma-core=28.9=h59595ed_1
- re2=2023.03.02=h8c504da_0
- readline=8.2=h8228510_1
- regex=2023.10.3=py310h2372a71_0
- requests=2.31.0=pyhd8ed1ab_0
- s2n=1.3.49=h06160fa_0
- sacremoses=0.0.53=pyhd8ed1ab_0
- safetensors=0.3.3=py310hcb5633a_1
- sentencepiece=0.1.99=hff52083_0
- sentencepiece-python=0.1.99=py310hebdb9f0_0
- sentencepiece-spm=0.1.99=h180e1df_0
- setuptools=68.2.2=pyhd8ed1ab_0
- six=1.16.0=pyh6c4a22f_0
- sleef=3.5.1=h9b69904_2
- snappy=1.1.10=h9fff704_0
- sympy=1.12=pypyh9d50eac_103
- tbb=2021.11.0=h00ab1b0_0
- texttable=1.7.0=pyhd8ed1ab_0
- timm=0.9.12=pyhd8ed1ab_0
- tk=8.6.13=noxft_h4845f30_101
- tokenizers=0.14.1=py310h320607d_2
- torchvision=0.14.1=cpu_py310hd3d2ac3_1
- tqdm=4.66.1=pyhd8ed1ab_0
- transformers=4.35.2=pyhd8ed1ab_0
- typing-extensions=4.9.0=hd8ed1ab_0
- typing_extensions=4.9.0=pyha770c72_0
- tzdata=2023c=h71feb2d_0
- ucx=1.14.1=h64cca9d_5
- urllib3=2.1.0=pyhd8ed1ab_0
- wcwidth=0.2.12=pyhd8ed1ab_0
- wheel=0.42.0=pyhd8ed1ab_0
- xorg-libxau=1.0.11=hd590300_0
- xorg-libxdmcp=1.1.3=h7f98852_0
- xxhash=0.8.2=hd590300_0
- xz=5.2.6=h166bdaf_0
- yaml=0.2.5=h7f98852_2
- yarl=1.9.3=py310h2372a71_0
- zipp=3.17.0=pyhd8ed1ab_0
- zlib=1.2.13=hd590300_5
- zstd=1.5.5=hfc55251_0
- pip:
- git+https://github.com/fyfrey/TinyNeuralNetwork.git

View File

@@ -0,0 +1,157 @@
import logging
import os
import platform
import subprocess
from abc import abstractmethod
import onnx
import open_clip
import torch
from onnx2torch import convert
from onnxruntime.tools.onnx_model_utils import fix_output_shapes, make_input_shape_fixed
from tinynn.converter import TFLiteConverter
class ExportBase(torch.nn.Module):
input_shape: tuple[int, ...]
def __init__(self, device: torch.device, name: str):
super().__init__()
self.device = device
self.name = name
self.optimize = 5
self.nchw_transpose = False
@abstractmethod
def forward(self, input_tensor: torch.Tensor) -> torch.Tensor | tuple[torch.Tensor]:
pass
def dummy_input(self) -> torch.FloatTensor:
return torch.rand((1, 3, 224, 224), device=self.device)
class ArcFace(ExportBase):
input_shape = (1, 3, 112, 112)
def __init__(self, onnx_model_path: str, device: torch.device):
name, _ = os.path.splitext(os.path.basename(onnx_model_path))
super().__init__(device, name)
onnx_model = onnx.load_model(onnx_model_path)
make_input_shape_fixed(onnx_model.graph, onnx_model.graph.input[0].name, self.input_shape)
fix_output_shapes(onnx_model)
self.model = convert(onnx_model).to(device)
if self.device.type == "cuda":
self.model = self.model.half()
def forward(self, input_tensor: torch.Tensor) -> torch.FloatTensor:
embedding: torch.FloatTensor = self.model(
input_tensor.half() if self.device.type == "cuda" else input_tensor
).float()
assert isinstance(embedding, torch.FloatTensor)
return embedding
def dummy_input(self) -> torch.FloatTensor:
return torch.rand(self.input_shape, device=self.device)
class RetinaFace(ExportBase):
input_shape = (1, 3, 640, 640)
def __init__(self, onnx_model_path: str, device: torch.device):
name, _ = os.path.splitext(os.path.basename(onnx_model_path))
super().__init__(device, name)
self.optimize = 3
self.model = convert(onnx_model_path).eval().to(device)
if self.device.type == "cuda":
self.model = self.model.half()
def forward(self, input_tensor: torch.Tensor) -> tuple[torch.FloatTensor]:
out: torch.Tensor = self.model(input_tensor.half() if self.device.type == "cuda" else input_tensor)
return tuple(o.float() for o in out)
def dummy_input(self) -> torch.FloatTensor:
return torch.rand(self.input_shape, device=self.device)
class ClipVision(ExportBase):
input_shape = (1, 3, 224, 224)
def __init__(self, model_name: str, weights: str, device: torch.device):
super().__init__(device, model_name + "__" + weights)
self.model = open_clip.create_model(
model_name,
weights,
precision="fp16" if device.type == "cuda" else "fp32",
jit=False,
require_pretrained=True,
device=device,
)
def forward(self, input_tensor: torch.Tensor) -> torch.FloatTensor:
embedding: torch.Tensor = self.model.encode_image(
input_tensor.half() if self.device.type == "cuda" else input_tensor,
normalize=True,
).float()
return embedding
def export(model: ExportBase) -> None:
model.eval()
for param in model.parameters():
param.requires_grad = False
dummy_input = model.dummy_input()
model(dummy_input)
jit = torch.jit.trace(model, dummy_input) # type: ignore[no-untyped-call,attr-defined]
tflite_model_path = f"output/{model.name}.tflite"
os.makedirs("output", exist_ok=True)
converter = TFLiteConverter(
jit,
dummy_input,
tflite_model_path,
optimize=model.optimize,
nchw_transpose=model.nchw_transpose,
)
# segfaults on ARM, must run on x86_64 / AMD64
converter.convert()
armnn_model_path = f"output/{model.name}.armnn"
os.environ["LD_LIBRARY_PATH"] = "armnn"
subprocess.run(
[
"./armnnconverter",
"-f",
"tflite-binary",
"-m",
tflite_model_path,
"-i",
"input_tensor",
"-o",
"output_tensor",
"-p",
armnn_model_path,
]
)
def main() -> None:
if platform.machine() not in ("x86_64", "AMD64"):
raise RuntimeError(f"Can only run on x86_64 / AMD64, not {platform.machine()}")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
if device.type != "cuda":
logging.warning(
"No CUDA available, cannot create fp16 model! proceeding to create a fp32 model (use only for testing)"
)
models = [
ClipVision("ViT-B-32", "openai", device),
ArcFace("buffalo_l_rec.onnx", device),
RetinaFace("buffalo_l_det.onnx", device),
]
for model in models:
export(model)
if __name__ == "__main__":
with torch.no_grad():
main()

View File

@@ -26,6 +26,7 @@ class Settings(BaseSettings):
request_threads: int = os.cpu_count() or 4
model_inter_op_threads: int = 1
model_intra_op_threads: int = 2
ann: bool = True
class Config:
env_prefix = "MACHINE_LEARNING_"

View File

@@ -5,10 +5,10 @@ from unittest import mock
import numpy as np
import pytest
from fastapi.testclient import TestClient
from numpy.typing import NDArray
from PIL import Image
from .main import app
from .schemas import ndarray_f32
@pytest.fixture
@@ -17,7 +17,7 @@ def pil_image() -> Image.Image:
@pytest.fixture
def cv_image(pil_image: Image.Image) -> ndarray_f32:
def cv_image(pil_image: Image.Image) -> NDArray[np.float32]:
return np.asarray(pil_image)[:, :, ::-1] # PIL uses RGB while cv2 uses BGR

View File

@@ -2,11 +2,11 @@ import asyncio
import gc
import os
import signal
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from typing import Any, Iterator
from contextlib import asynccontextmanager
from typing import Any, AsyncGenerator, Callable, Iterator
from zipfile import BadZipFile
import orjson
@@ -26,7 +26,6 @@ from .schemas import (
)
MultiPartParser.max_file_size = 2**26 # spools to disk if payload is 64 MiB or larger
app = FastAPI()
model_cache = ModelCache(ttl=settings.model_ttl, revalidate=settings.model_ttl > 0)
thread_pool: ThreadPoolExecutor | None = None
@@ -35,8 +34,8 @@ active_requests = 0
last_called: float | None = None
@app.on_event("startup")
def startup() -> None:
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
global thread_pool
log.info(
(
@@ -44,21 +43,22 @@ def startup() -> None:
f"{f'after {settings.model_ttl}s of inactivity' if settings.model_ttl > 0 else 'disabled'}."
)
)
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None
if settings.model_ttl > 0 and settings.model_ttl_poll_s > 0:
asyncio.ensure_future(idle_shutdown_task())
log.info(f"Initialized request thread pool with {settings.request_threads} threads.")
@app.on_event("shutdown")
def shutdown() -> None:
log.handlers.clear()
for model in model_cache.cache._cache.values():
del model
if thread_pool is not None:
thread_pool.shutdown()
gc.collect()
try:
if settings.request_threads > 0:
# asyncio is a huge bottleneck for performance, so we use a thread pool to run blocking code
thread_pool = ThreadPoolExecutor(settings.request_threads) if settings.request_threads > 0 else None
log.info(f"Initialized request thread pool with {settings.request_threads} threads.")
if settings.model_ttl > 0 and settings.model_ttl_poll_s > 0:
asyncio.ensure_future(idle_shutdown_task())
yield
finally:
log.handlers.clear()
for model in model_cache.cache._cache.values():
del model
if thread_pool is not None:
thread_pool.shutdown()
gc.collect()
def update_state() -> Iterator[None]:
@@ -71,6 +71,9 @@ def update_state() -> Iterator[None]:
active_requests -= 1
app = FastAPI(lifespan=lifespan)
@app.get("/", response_model=MessageResponse)
async def root() -> dict[str, str]:
return {"message": "Immich ML"}
@@ -102,14 +105,14 @@ async def predict(
model = await load(await model_cache.get(model_name, model_type, **kwargs))
model.configure(**kwargs)
outputs = await run(model, inputs)
outputs = await run(model.predict, inputs)
return ORJSONResponse(outputs)
async def run(model: InferenceModel, inputs: Any) -> Any:
async def run(func: Callable[..., Any], inputs: Any) -> Any:
if thread_pool is None:
return model.predict(inputs)
return await asyncio.get_running_loop().run_in_executor(thread_pool, model.predict, inputs)
return func(inputs)
return await asyncio.get_running_loop().run_in_executor(thread_pool, func, inputs)
async def load(model: InferenceModel) -> InferenceModel:

View File

@@ -0,0 +1,68 @@
from __future__ import annotations
from pathlib import Path
from typing import Any, NamedTuple
import numpy as np
from numpy.typing import NDArray
from ann.ann import Ann
from ..config import log, settings
class AnnSession:
"""
Wrapper for ANN to be drop-in replacement for ONNX session.
"""
def __init__(self, model_path: Path):
tuning_file = Path(settings.cache_folder) / "gpu-tuning.ann"
with tuning_file.open(mode="a"):
# make sure tuning file exists (without clearing contents)
# once filled, the tuning file reduces the cost/time of the first
# inference after model load by 10s of seconds
pass
self.ann = Ann(tuning_level=3, tuning_file=tuning_file.as_posix())
log.info("Loading ANN model %s ...", model_path)
cache_file = model_path.with_suffix(".anncache")
save = False
if not cache_file.is_file():
save = True
with cache_file.open(mode="a"):
# create empty model cache file
pass
self.model = self.ann.load(
model_path.as_posix(),
save_cached_network=save,
cached_network_path=cache_file.as_posix(),
)
log.info("Loaded ANN model with ID %d", self.model)
def __del__(self) -> None:
self.ann.unload(self.model)
log.info("Unloaded ANN model %d", self.model)
self.ann.destroy()
def get_inputs(self) -> list[AnnNode]:
shapes = self.ann.input_shapes[self.model]
return [AnnNode(None, s) for s in shapes]
def get_outputs(self) -> list[AnnNode]:
shapes = self.ann.output_shapes[self.model]
return [AnnNode(None, s) for s in shapes]
def run(
self,
output_names: list[str] | None,
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
run_options: Any = None,
) -> list[NDArray[np.float32]]:
inputs: list[NDArray[np.float32]] = [np.ascontiguousarray(v) for v in input_feed.values()]
return self.ann.execute(self.model, inputs)
class AnnNode(NamedTuple):
name: str | None
shape: tuple[int, ...]

View File

@@ -10,8 +10,11 @@ import onnxruntime as ort
from huggingface_hub import snapshot_download
from typing_extensions import Buffer
import ann.ann
from ..config import get_cache_dir, get_hf_model_name, log, settings
from ..schemas import ModelType
from .ann import AnnSession
class InferenceModel(ABC):
@@ -138,6 +141,21 @@ class InferenceModel(ABC):
self.cache_dir.unlink()
self.cache_dir.mkdir(parents=True, exist_ok=True)
def _make_session(self, model_path: Path) -> AnnSession | ort.InferenceSession:
armnn_path = model_path.with_suffix(".armnn")
if settings.ann and ann.ann.is_available and armnn_path.is_file():
session = AnnSession(armnn_path)
elif model_path.is_file():
session = ort.InferenceSession(
model_path.as_posix(),
sess_options=self.sess_options,
providers=self.providers,
provider_options=self.provider_options,
)
else:
raise ValueError(f"the file model_path='{model_path}' does not exist")
return session
# HF deep copies configs, so we need to make session options picklable
class PicklableSessionOptions(ort.SessionOptions): # type: ignore[misc]

View File

@@ -6,13 +6,13 @@ from pathlib import Path
from typing import Any, Literal
import numpy as np
import onnxruntime as ort
from numpy.typing import NDArray
from PIL import Image
from tokenizers import Encoding, Tokenizer
from app.config import clean_name, log
from app.models.transforms import crop, get_pil_resampling, normalize, resize, to_numpy
from app.schemas import ModelType, ndarray_f32, ndarray_i32
from app.schemas import ModelType
from .base import InferenceModel
@@ -33,27 +33,15 @@ class BaseCLIPEncoder(InferenceModel):
def _load(self) -> None:
if self.mode == "text" or self.mode is None:
log.debug(f"Loading clip text model '{self.model_name}'")
self.text_model = ort.InferenceSession(
self.textual_path.as_posix(),
sess_options=self.sess_options,
providers=self.providers,
provider_options=self.provider_options,
)
self.text_model = self._make_session(self.textual_path)
log.debug(f"Loaded clip text model '{self.model_name}'")
if self.mode == "vision" or self.mode is None:
log.debug(f"Loading clip vision model '{self.model_name}'")
self.vision_model = ort.InferenceSession(
self.visual_path.as_posix(),
sess_options=self.sess_options,
providers=self.providers,
provider_options=self.provider_options,
)
self.vision_model = self._make_session(self.visual_path)
log.debug(f"Loaded clip vision model '{self.model_name}'")
def _predict(self, image_or_text: Image.Image | str) -> ndarray_f32:
def _predict(self, image_or_text: Image.Image | str) -> NDArray[np.float32]:
if isinstance(image_or_text, bytes):
image_or_text = Image.open(BytesIO(image_or_text))
@@ -61,12 +49,10 @@ class BaseCLIPEncoder(InferenceModel):
case Image.Image():
if self.mode == "text":
raise TypeError("Cannot encode image as text-only model")
outputs: ndarray_f32 = self.vision_model.run(None, self.transform(image_or_text))[0][0]
outputs: NDArray[np.float32] = self.vision_model.run(None, self.transform(image_or_text))[0][0]
case str():
if self.mode == "vision":
raise TypeError("Cannot encode text as vision-only model")
outputs = self.text_model.run(None, self.tokenize(image_or_text))[0][0]
case _:
raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}")
@@ -74,11 +60,11 @@ class BaseCLIPEncoder(InferenceModel):
return outputs
@abstractmethod
def tokenize(self, text: str) -> dict[str, ndarray_i32]:
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
pass
@abstractmethod
def transform(self, image: Image.Image) -> dict[str, ndarray_f32]:
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
pass
@property
@@ -176,11 +162,11 @@ class OpenCLIPEncoder(BaseCLIPEncoder):
self.tokenizer.enable_truncation(max_length=context_length)
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
def tokenize(self, text: str) -> dict[str, ndarray_i32]:
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
tokens: Encoding = self.tokenizer.encode(text)
return {"text": np.array([tokens.ids], dtype=np.int32)}
def transform(self, image: Image.Image) -> dict[str, ndarray_f32]:
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
image = resize(image, self.size)
image = crop(image, self.size)
image_np = to_numpy(image)
@@ -189,7 +175,7 @@ class OpenCLIPEncoder(BaseCLIPEncoder):
class MCLIPEncoder(OpenCLIPEncoder):
def tokenize(self, text: str) -> dict[str, ndarray_i32]:
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
tokens: Encoding = self.tokenizer.encode(text)
return {
"input_ids": np.array([tokens.ids], dtype=np.int32),

View File

@@ -3,12 +3,12 @@ from typing import Any
import cv2
import numpy as np
import onnxruntime as ort
from insightface.model_zoo import ArcFaceONNX, RetinaFace
from insightface.utils.face_align import norm_crop
from numpy.typing import NDArray
from app.config import clean_name
from app.schemas import BoundingBox, Face, ModelType, ndarray_f32
from app.schemas import Face, ModelType, is_ndarray
from .base import InferenceModel
@@ -27,23 +27,8 @@ class FaceRecognizer(InferenceModel):
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
def _load(self) -> None:
self.det_model = RetinaFace(
session=ort.InferenceSession(
self.det_file.as_posix(),
sess_options=self.sess_options,
providers=self.providers,
provider_options=self.provider_options,
),
)
self.rec_model = ArcFaceONNX(
self.rec_file.as_posix(),
session=ort.InferenceSession(
self.rec_file.as_posix(),
sess_options=self.sess_options,
providers=self.providers,
provider_options=self.provider_options,
),
)
self.det_model = RetinaFace(session=self._make_session(self.det_file))
self.rec_model = ArcFaceONNX(self.rec_file.as_posix(), session=self._make_session(self.rec_file))
self.det_model.prepare(
ctx_id=0,
@@ -52,22 +37,25 @@ class FaceRecognizer(InferenceModel):
)
self.rec_model.prepare(ctx_id=0)
def _predict(self, image: ndarray_f32 | bytes) -> list[Face]:
def _predict(self, image: NDArray[np.uint8] | bytes) -> list[Face]:
if isinstance(image, bytes):
image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR)
bboxes, kpss = self.det_model.detect(image)
decoded_image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR)
else:
decoded_image = image
assert is_ndarray(decoded_image, np.uint8)
bboxes, kpss = self.det_model.detect(decoded_image)
if bboxes.size == 0:
return []
assert isinstance(image, np.ndarray) and isinstance(kpss, np.ndarray)
assert is_ndarray(kpss, np.float32)
scores = bboxes[:, 4].tolist()
bboxes = bboxes[:, :4].round().tolist()
results = []
height, width, _ = image.shape
height, width, _ = decoded_image.shape
for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss):
cropped_img = norm_crop(image, kps)
embedding: ndarray_f32 = self.rec_model.get_feat(cropped_img)[0]
cropped_img = norm_crop(decoded_image, kps)
embedding: NDArray[np.float32] = self.rec_model.get_feat(cropped_img)[0]
face: Face = {
"imageWidth": width,
"imageHeight": height,

View File

@@ -1,8 +1,7 @@
import numpy as np
from numpy.typing import NDArray
from PIL import Image
from app.schemas import ndarray_f32
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
@@ -23,11 +22,13 @@ def crop(img: Image.Image, size: int) -> Image.Image:
return img.crop((left, upper, right, lower))
def to_numpy(img: Image.Image) -> ndarray_f32:
def to_numpy(img: Image.Image) -> NDArray[np.float32]:
return np.asarray(img.convert("RGB")).astype(np.float32) / 255.0
def normalize(img: ndarray_f32, mean: float | ndarray_f32, std: float | ndarray_f32) -> ndarray_f32:
def normalize(
img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32]
) -> NDArray[np.float32]:
return (img - mean) / std

View File

@@ -1,13 +1,10 @@
from enum import StrEnum
from typing import Any, Protocol, TypeAlias, TypedDict, TypeGuard
from typing import Any, Protocol, TypedDict, TypeGuard
import numpy as np
import numpy.typing as npt
from pydantic import BaseModel
ndarray_f32: TypeAlias = np.ndarray[int, np.dtype[np.float32]]
ndarray_i64: TypeAlias = np.ndarray[int, np.dtype[np.int64]]
ndarray_i32: TypeAlias = np.ndarray[int, np.dtype[np.int32]]
class TextResponse(BaseModel):
__root__: str
@@ -35,7 +32,7 @@ class HasProfiling(Protocol):
class Face(TypedDict):
boundingBox: BoundingBox
embedding: ndarray_f32
embedding: npt.NDArray[np.float32]
imageWidth: int
imageHeight: int
score: float
@@ -43,3 +40,7 @@ class Face(TypedDict):
def has_profiling(obj: Any) -> TypeGuard[HasProfiling]:
return hasattr(obj, "profiling") and isinstance(obj.profiling, dict)
def is_ndarray(obj: Any, dtype: "type[np._DTypeScalar_co]") -> "TypeGuard[npt.NDArray[np._DTypeScalar_co]]":
return isinstance(obj, np.ndarray) and obj.dtype == dtype

View File

@@ -13,7 +13,7 @@ from PIL import Image
from pytest_mock import MockerFixture
from .config import settings
from .models.base import PicklableSessionOptions
from .models.base import InferenceModel, PicklableSessionOptions
from .models.cache import ModelCache
from .models.clip import OpenCLIPEncoder
from .models.facial_recognition import FaceRecognizer
@@ -36,9 +36,10 @@ class TestCLIP:
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
mocked.run.return_value = [[self.embedding]]
mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True)
mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True)
mocked.return_value.run.return_value = [[self.embedding]]
clip_encoder = OpenCLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", mode="vision")
embedding = clip_encoder.predict(pil_image)
@@ -47,7 +48,7 @@ class TestCLIP:
assert isinstance(embedding, np.ndarray)
assert embedding.shape[0] == clip_model_cfg["embed_dim"]
assert embedding.dtype == np.float32
clip_encoder.vision_model.run.assert_called_once()
mocked.run.assert_called_once()
def test_basic_text(
self,
@@ -60,9 +61,10 @@ class TestCLIP:
mocker.patch.object(OpenCLIPEncoder, "model_cfg", clip_model_cfg)
mocker.patch.object(OpenCLIPEncoder, "preprocess_cfg", clip_preprocess_cfg)
mocker.patch.object(OpenCLIPEncoder, "tokenizer_cfg", clip_tokenizer_cfg)
mocked = mocker.patch.object(InferenceModel, "_make_session", autospec=True).return_value
mocked.run.return_value = [[self.embedding]]
mocker.patch("app.models.clip.Tokenizer.from_file", autospec=True)
mocked = mocker.patch("app.models.clip.ort.InferenceSession", autospec=True)
mocked.return_value.run.return_value = [[self.embedding]]
clip_encoder = OpenCLIPEncoder("ViT-B-32::openai", cache_dir="test_cache", mode="text")
embedding = clip_encoder.predict("test search query")
@@ -71,7 +73,7 @@ class TestCLIP:
assert isinstance(embedding, np.ndarray)
assert embedding.shape[0] == clip_model_cfg["embed_dim"]
assert embedding.dtype == np.float32
clip_encoder.text_model.run.assert_called_once()
mocked.run.assert_called_once()
class TestFaceRecognition:

View File

@@ -1,4 +1,4 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:a7033851ca71454ed8cfbc958fdd62a72e6fa90f71ccb3aa9b15e67a98f66a1e as builder
FROM mambaorg/micromamba:bookworm-slim@sha256:377aafafb5f58e577faeb8b2884b795c6ff0a92e4a53ef814ea3961286ceae18 as builder
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \

View File

@@ -1,7 +1,6 @@
import tempfile
import warnings
from dataclasses import dataclass, field
from math import e
from pathlib import Path
import open_clip

View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]]
name = "aiocache"
@@ -63,33 +63,33 @@ trio = ["trio (<0.22)"]
[[package]]
name = "black"
version = "23.12.0"
version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"},
{file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"},
{file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"},
{file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"},
{file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"},
{file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"},
{file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"},
{file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"},
{file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"},
{file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"},
{file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"},
{file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"},
{file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"},
{file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"},
{file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"},
{file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"},
{file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"},
{file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"},
{file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"},
{file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"},
{file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"},
{file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"},
{file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
{file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
{file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
{file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
{file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
{file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
{file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
{file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
{file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
{file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
{file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
{file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
{file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
{file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
{file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
{file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
{file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
{file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
{file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
{file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
{file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
{file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
]
[package.dependencies]
@@ -588,62 +588,53 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"]
[[package]]
name = "cython"
version = "3.0.7"
description = "The Cython compiler for writing C extensions in the Python language."
version = "0.29.37"
description = "The Cython compiler for writing C extensions for the Python language."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "Cython-3.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3c0e19bb41de6be9d8afc85795159ca16296be81a586cd9588be0400d44a855"},
{file = "Cython-3.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e8bf00ec1dd1d92e9ae74d2e6891f087a939e1dfb40c9c7fa5d8d6a26c94f5a"},
{file = "Cython-3.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd6ae43ef2e596c9a88dbf2a8895be2e32cc2f5bc3c8ba2e7753b69068fc0b2d"},
{file = "Cython-3.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f674be92673e87dd8ee7cfe553d5960ec4effc5ab15063b9a5e265a51585a31a"},
{file = "Cython-3.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:861cf254bf5836d47c2aee86aa75dd93d3de00ccd1b077c3c7a2bb22cba358e7"},
{file = "Cython-3.0.7-cp310-cp310-win32.whl", hash = "sha256:f6d8ff62ad55dc0393686438eac4b457a916e4d1118a0b550746bb52b4c756cc"},
{file = "Cython-3.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:e13abb14843397b76d0472c7d33cd260d5f262ab05cc27ed423317e645e29643"},
{file = "Cython-3.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c636c9ab92c7838231a1ba769e519d953af8294612f3f772a54d3a5250ff23f"},
{file = "Cython-3.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d2a684122dfb531853d57c8c85c1d5d44be709e12466dca99fa6aee7d8054f"},
{file = "Cython-3.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1bdf8a107fdf9e174991aa87a0be7504f60de1ec6bfb1ccfb30e33acac818a0"},
{file = "Cython-3.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3a83e04fde663b84905f3a20213a4333d13a07b79434300704b70dc552761f8b"},
{file = "Cython-3.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e34b4b08d795ccca920fa26b099558f4f1e4e3f794e4ba8d3433c5bc2454d50a"},
{file = "Cython-3.0.7-cp311-cp311-win32.whl", hash = "sha256:133057ac45b6fa7fe5d7baada9d3545d09339432f75c0545f556e8c6fecc2932"},
{file = "Cython-3.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:b65abca78aa5ebc8675c8480b9a53006f6efea9910ad099cf32c9fb5617ef251"},
{file = "Cython-3.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ceac5315fe899c229e874328742154e331fa41337bb03f6f5264636c351c9e"},
{file = "Cython-3.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea936cf5931297ba07bce121388c4c6266c1b63a9f4d648ae16c92ff090204b"},
{file = "Cython-3.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fcd9a18ee3ac7f460e0841954feb495102ffbdbec0e6c78562f3495cda000dd"},
{file = "Cython-3.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c8d579d13cb81abe704c8b0908d122b81d6e2623265a19c4a6a7377f440debb"},
{file = "Cython-3.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ef5bb0268bfe5992da3ef9292463a5a895ed8700b134ed2c00008d5471b3ba6e"},
{file = "Cython-3.0.7-cp312-cp312-win32.whl", hash = "sha256:55f93d3822bc196b37a8bdfa4ec6a35232a399e97f2baa714bd5ed8ea9b0ce68"},
{file = "Cython-3.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:f3845c4506e0d207c5e268fb02813928f3a1e135de954a379f165ef0d581da47"},
{file = "Cython-3.0.7-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ad7c2303a338b2c0b6c6c68f101a6768725934538756096cf3388a5c07a7525"},
{file = "Cython-3.0.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed25959e4025870fdde5f895fcb126196d22affd4f4fad85a2823e0dddc85b0"},
{file = "Cython-3.0.7-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79868ec74e4907a8a6e63effe13547c6157f196a162920b1de066da5849ffb8e"},
{file = "Cython-3.0.7-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5e3a038332973b12e72236e8884dc99601a840334c2c46cfbbb5851cb94166eb"},
{file = "Cython-3.0.7-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f2602a5c97a3d618b3b847514204ef3349fb414c59e1126c0c2c708d2c5680f8"},
{file = "Cython-3.0.7-cp36-cp36m-win32.whl", hash = "sha256:539ad5a21141e6420035cf616bcba48d999bf878839e52692f97fc7e2f16265c"},
{file = "Cython-3.0.7-cp36-cp36m-win_amd64.whl", hash = "sha256:848a28ea49166454c3bff927e5a47629eecf1aa755d6fb3290569cba0fc93766"},
{file = "Cython-3.0.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f27a0134fc6bb46032ca5f728d8af984f3be94a3cb01cb70ff1224e551b9cf"},
{file = "Cython-3.0.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f20c61114c7948cf1214585066406cef4b54a9b935160980e0b6e70ada3a69"},
{file = "Cython-3.0.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d51709e10ad6213b4bf094af7be7ff82bab43216b3c92a07d05b451deeca79"},
{file = "Cython-3.0.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3f02c7240abab48d59f0d5fef7064f18f01a2a204616165fa6367a8abf5a8832"},
{file = "Cython-3.0.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:225f8bba6428b8d711ca2d6c738d2e3a4667f6a2ae40f8a7a5256f69f6a3600e"},
{file = "Cython-3.0.7-cp37-cp37m-win32.whl", hash = "sha256:30eb2d2938b9195e2c82951713429aff3ad1be9f104437d1536a04eb0cb3dc0e"},
{file = "Cython-3.0.7-cp37-cp37m-win_amd64.whl", hash = "sha256:167b3f3894dcc697cefefac1d198304fae8eb4d5860a7b8bc2459d572e838470"},
{file = "Cython-3.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c67105f2c6ccf5b3adbcfaecf3c5c9fa8940f9f97955c9ad7d2542151d97d93"},
{file = "Cython-3.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a1859af761977530df2cd5c36e31d54e8d6708ad2c4656e7125c482364dc216"},
{file = "Cython-3.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01b94304aab87496e81d1f546e71abf57b430b39be4269df1cd7da9928d70b5b"},
{file = "Cython-3.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:931aade65f77cf59f2a702ac1f549a4836ce221107c740502cbad18d6d8e9511"},
{file = "Cython-3.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:812b193c26553f1f375d4f1c50f805c227b24ed2d595bc9cdaf78c992ecc64a4"},
{file = "Cython-3.0.7-cp38-cp38-win32.whl", hash = "sha256:b227643d8a40b68554dc7d37fcd03fc97b4fb0bd2614aeb5f2e07ab244642d36"},
{file = "Cython-3.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:0d8a98c7d86ac4d05b251c39faf49423780381aab55fbf2e147f6e006a34a58a"},
{file = "Cython-3.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:816f5285d596062c7ef22790de7d75354b58d4417a9fc64cba914aeeb900db0b"},
{file = "Cython-3.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9d0dae6dccd349b8ccf197c10ef2d05c711ca36a649c7eddbab1de2c90b63a1"},
{file = "Cython-3.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13211b67b29f6ed8e87c137496c73d93aff0330d97940b4fbed72eae37a4a2a0"},
{file = "Cython-3.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1853bc34ced5ff6473e881fcf6de29da83262552c8f268a0df53b49c2b89e2c"},
{file = "Cython-3.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:51e8164b1270625ff101e95c3c1c234421520c07a0a3a20ded9e9431d98afce7"},
{file = "Cython-3.0.7-cp39-cp39-win32.whl", hash = "sha256:45319d2471f4dbf19893ca53785a421107266e18b8cccd2054fce1e3f72a85f1"},
{file = "Cython-3.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:612d83fd1eb5aaa5401a755c1f1aafacd9dab404cd350b90d5f404c98b33e4b3"},
{file = "Cython-3.0.7-py2.py3-none-any.whl", hash = "sha256:936ec37b261b226d7404eff23a9aad284098338150d42a53d6a9af12b18d3892"},
{file = "Cython-3.0.7.tar.gz", hash = "sha256:fb299acf3a578573c190c858d49e0cf9d75f4bc49c3f24c5a63804997ef09213"},
{file = "Cython-0.29.37-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2d621fe4cb50007446742134a890500b34e3f50abaf7993baaca02634af7e15"},
{file = "Cython-0.29.37-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d94caf90ae9cb56116ca6d54cdcbccd3c4df6b0cb7233922b2233ee7fe81d05b"},
{file = "Cython-0.29.37-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:852cd4378cbc9ade02f53709107ff9fdad55019a3a636e8a27663ba6cfce10b6"},
{file = "Cython-0.29.37-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bbce388431a2608a81c8ab13cb14c50611473843ca766031b8b24bb1723faf79"},
{file = "Cython-0.29.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4658499a41255431f6bbdca7e634e9c8d3a4c190bf24b4aa1646dac751d3da4d"},
{file = "Cython-0.29.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:12192ab269e7185720f2d2f8894587bf1da4276db1b9b869e4622a093f18cae6"},
{file = "Cython-0.29.37-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9450e0766ab65947f8a2a36f9e59079fc879c3807ec936c61725a48c97741a52"},
{file = "Cython-0.29.37-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:177481b0a7e003e5c49e2bf0dda1d6fe610c239f17642a5da9f18c2ad0c5f6b6"},
{file = "Cython-0.29.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b048354fd380278f2fa096e7526973beb6e0491a9d44d7e4e29df52612d25776"},
{file = "Cython-0.29.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea6d208be1906c5df25b674777d5905c6d8e9ef0b201b830849e0729ba08caba"},
{file = "Cython-0.29.37-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:af03854571738307a5f30cc6b724081d72db12f907699e7fdfc04c12c839158e"},
{file = "Cython-0.29.37-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c33508ede9172a6f6f99d5a6dadc7fee23c840423b411ef8b5a403c04e530297"},
{file = "Cython-0.29.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8af5975ecfae254d8c0051204fca995dda8f93cf9f0bbf7571e3cda2b0cef4d"},
{file = "Cython-0.29.37-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29415d8eb2fdc1ea518ca4810c50a2d062b387d4c9fbcfb3352346e93db22c6d"},
{file = "Cython-0.29.37-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe0eaf6b1e9ee97c5ee7bfc943f00e36cf59d929db16886cb018352bff8208da"},
{file = "Cython-0.29.37-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc1b9ce2b73b9ee8c305e06173b35c7c202d4b82d084a0cd73dcedfd6d310aec"},
{file = "Cython-0.29.37-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2618af0b8df26d32ee4e8858d4ad8167546596762620aeade84954ae37194a0e"},
{file = "Cython-0.29.37-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ac910a28a2fd3d280faf3077b6fe63b97a4b93994ff05647581846f0e4b2f8d1"},
{file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:8bf38373773f967cfd793997a6fb96cf972d41a9fce987ace5767349d6f15572"},
{file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cddb567dadb3aa3e280a8a35e5126030915ea744c2812206e9c194b8881475d"},
{file = "Cython-0.29.37-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:79ecfc48694e156402c05561e0adb0e25a6e9d35ac0b41693733a08219d38c58"},
{file = "Cython-0.29.37-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:9a455347e20ddfad0c5dfee32a3e855ee96811269e5fd86be622ddc4cb326404"},
{file = "Cython-0.29.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:fa5b6a0f69bf1823c9fd038fa77a2568b78fda2de045a95b48a71dee4d0d578f"},
{file = "Cython-0.29.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a6164a05440dcd9daa760c6488bc91bdac1380c7b4b3aca38cf307ba66042d54"},
{file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:562f8f911dbd6f1a1b9be8f6cba097125700355688f613994ccd4406f220557a"},
{file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c39c2f5a0fe29bb01de9b1fb449bf65bed6f192317c677f181732791c63fe28"},
{file = "Cython-0.29.37-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0a0a6d5972bb3b8c7363cf19a42a988bb0c0bb5ebd9c736c84eca85113ccfdbe"},
{file = "Cython-0.29.37-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b82584836e9e7c0d6effee976595e5cd7fa88dbef3e96e900187983c1d4637d1"},
{file = "Cython-0.29.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b6c48f1032b379135a5b4a31976d6c468e02490688acf9254c6c8ed27bd4cbd4"},
{file = "Cython-0.29.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f87bef1808d255cf13be378c7ad27ae7c6db6df7732217d32428d1daf4109be"},
{file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:9e68bafeeb97d5a403fb1f7700bd4a55a1f8989824c323ae02ae8a4fcd88f6a1"},
{file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e14cd44c830e53cf9d7269c87a6bcc638bb065ec07e24990e338162c7001d3c3"},
{file = "Cython-0.29.37-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0544f7a3e4437b89b356baa15387494c18214e03f2ffaddada5a2c71c3dfd24b"},
{file = "Cython-0.29.37-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2de3e729d25f041036e81e2f15683dd129f977dfb5b06267e30e8d7acec43225"},
{file = "Cython-0.29.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2ad634dc77a6a74022881826099eccac19c9b79153942cc82e754ffac2bec116"},
{file = "Cython-0.29.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e841a8b4f9ceefb2916e32dac4f28a895cd519e8ece71505144da1ee355c548a"},
{file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:6c672089fba6a8f6690b8d7924a58c04477771401ad101d53171a13405ee12cb"},
{file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0301d4739c6894e012f1d410052082fdda9e63888c815d9e23e0f7f82fff7d79"},
{file = "Cython-0.29.37-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af8e7b4397620e2d18259a11f3bfa026eff9846657e397d02616962dd5dd035a"},
{file = "Cython-0.29.37-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b225d5e2091c224d4ab328165fef224ba3919b3ed44bd9b3241416f523b4d51a"},
{file = "Cython-0.29.37-py2.py3-none-any.whl", hash = "sha256:95f1d6a83ef2729e67b3fa7318c829ce5b07ac64c084cd6af11c228e0364662c"},
{file = "Cython-0.29.37.tar.gz", hash = "sha256:f813d4a6dd94adee5d4ff266191d1d95bf6d4164a4facc535422c021b2504cfb"},
]
[[package]]
@@ -672,19 +663,18 @@ test = ["pytest (>=6)"]
[[package]]
name = "fastapi"
version = "0.106.0"
version = "0.109.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.8"
files = [
{file = "fastapi-0.106.0-py3-none-any.whl", hash = "sha256:193c2f1b495d1d6561a3dc1ca02a150757322247d895ff6bf15b6eefee24feb9"},
{file = "fastapi-0.106.0.tar.gz", hash = "sha256:c7e7453ac8c4b7414bbafcf90127d743559808eee286ae2c9f99a501f0b534a0"},
{file = "fastapi-0.109.0-py3-none-any.whl", hash = "sha256:8c77515984cd8e8cfeb58364f8cc7a28f0692088475e2614f7bf03275eba9093"},
{file = "fastapi-0.109.0.tar.gz", hash = "sha256:b978095b9ee01a5cf49b19f4bc1ac9b8ca83aa076e770ef8fd9af09a2b88d191"},
]
[package.dependencies]
anyio = ">=3.7.1,<4.0.0"
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.27.0,<0.28.0"
starlette = ">=0.35.0,<0.36.0"
typing-extensions = ">=4.8.0"
[package.extras]
@@ -1265,13 +1255,13 @@ socks = ["socksio (==1.*)"]
[[package]]
name = "huggingface-hub"
version = "0.20.1"
version = "0.20.2"
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "huggingface_hub-0.20.1-py3-none-any.whl", hash = "sha256:ecfdea395a8bc68cd160106c5bd857f7e010768d95f9e1862a779010cc304831"},
{file = "huggingface_hub-0.20.1.tar.gz", hash = "sha256:8c88c4c3c8853e22f2dfb4d84c3d493f4e1af52fb3856a90e1eeddcf191ddbb1"},
{file = "huggingface_hub-0.20.2-py3-none-any.whl", hash = "sha256:53752eda2239d30a470c307a61cf9adcf136bc77b0a734338c7d04941af560d8"},
{file = "huggingface_hub-0.20.2.tar.gz", hash = "sha256:215c5fceff631030c7a3d19ba7b588921c908b3f21eef31d160ebc245b200ff6"},
]
[package.dependencies]
@@ -1557,13 +1547,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
[[package]]
name = "locust"
version = "2.20.0"
version = "2.20.1"
description = "Developer friendly load testing framework"
optional = false
python-versions = ">=3.8"
files = [
{file = "locust-2.20.0-py3-none-any.whl", hash = "sha256:8092dde31c4cfb43b9b40470ffc7c5f95b3a491dfcb813bb50567edec28451dc"},
{file = "locust-2.20.0.tar.gz", hash = "sha256:b6f78af64bc5066babe54836f796469906cd606b01f34ee788986d0b1fbac99a"},
{file = "locust-2.20.1-py3-none-any.whl", hash = "sha256:70168ccf462f125e0e4304c1f8301d3c18f186e8f257bc6578e7bed5e74e59a7"},
{file = "locust-2.20.1.tar.gz", hash = "sha256:9ba4c8658a158aed55774ac3650ac0139fcc1dfa65fea0dabb00ea35b0d56a4e"},
]
[package.dependencies]
@@ -1577,7 +1567,7 @@ msgpack = ">=1.0.0"
psutil = ">=5.9.1"
pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
pyzmq = ">=25.0.0"
requests = ">=2.23.0"
requests = ">=2.26.0"
roundrobin = ">=0.0.2"
Werkzeug = ">=2.0.0"
@@ -1817,38 +1807,38 @@ files = [
[[package]]
name = "mypy"
version = "1.7.1"
version = "1.8.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"},
{file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"},
{file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"},
{file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"},
{file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"},
{file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
{file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
{file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
{file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
{file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
{file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
{file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
{file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
{file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
{file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
{file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"},
{file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"},
{file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"},
{file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"},
{file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"},
{file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"},
{file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"},
{file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"},
{file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"},
{file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"},
{file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
{file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
{file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"},
{file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"},
{file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"},
{file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"},
{file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"},
{file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"},
{file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"},
{file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"},
{file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"},
{file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"},
{file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"},
{file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"},
{file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"},
{file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"},
{file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"},
{file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"},
{file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"},
{file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"},
{file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"},
{file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"},
{file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"},
{file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"},
{file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"},
{file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"},
{file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"},
{file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"},
{file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"},
]
[package.dependencies]
@@ -2020,18 +2010,18 @@ sympy = "*"
[[package]]
name = "opencv-python-headless"
version = "4.8.1.78"
version = "4.9.0.80"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
{file = "opencv-python-headless-4.8.1.78.tar.gz", hash = "sha256:bc7197b42352f6f865c302a49140b889ec7cd957dd697e2d7fc016ad0d3f28f1"},
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f3a33f644249f9ce1c913eac580e4b3ef4ce7cab0a71900274708959c2feb5e3"},
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:2c7d45721df9801c4dcd34683a15caa0e30f38b185263fec04a6eb274bc720f0"},
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b6bd6e1132b6f5dcb3a5bfe30fc4d341a7bfb26134da349a06c9255288ded94"},
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58e70d2f0915fe23e02c6e405588276c9397844a47d38b9c87fac5f7f9ba2dcc"},
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:382f8c7a6a14f80091284eecedd52cee4812231ee0eff1118592197b538d9252"},
{file = "opencv_python_headless-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:0a0f1e9f836f7d5bad1dd164694944c8761711cbdf4b36ebbd4815a8ef731079"},
{file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"},
{file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"},
]
[package.dependencies]
@@ -2124,70 +2114,88 @@ files = [
[[package]]
name = "pillow"
version = "10.1.0"
version = "10.2.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.8"
files = [
{file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"},
{file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"},
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"},
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"},
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"},
{file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"},
{file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"},
{file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"},
{file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"},
{file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"},
{file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"},
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"},
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"},
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"},
{file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"},
{file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"},
{file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"},
{file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"},
{file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"},
{file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"},
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"},
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"},
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"},
{file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"},
{file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"},
{file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"},
{file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"},
{file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"},
{file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"},
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"},
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"},
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"},
{file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"},
{file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"},
{file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"},
{file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"},
{file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"},
{file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"},
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"},
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"},
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"},
{file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"},
{file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"},
{file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"},
{file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"},
{file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"},
{file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"},
{file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"},
{file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"},
{file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"},
{file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"},
{file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"},
{file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"},
{file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"},
{file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"},
{file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"},
{file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"},
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"},
{file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"},
{file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"},
{file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"},
{file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"},
{file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"},
{file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"},
{file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"},
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"},
{file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"},
{file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"},
{file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"},
{file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"},
{file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"},
{file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"},
{file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"},
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"},
{file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"},
{file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"},
{file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"},
{file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"},
{file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"},
{file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"},
{file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"},
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"},
{file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"},
{file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"},
{file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"},
{file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"},
{file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"},
{file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"},
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"},
{file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"},
{file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"},
{file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"},
{file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"},
{file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"},
{file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"},
{file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"},
{file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"},
]
[package.extras]
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
name = "platformdirs"
@@ -2389,13 +2397,13 @@ files = [
[[package]]
name = "pytest"
version = "7.4.3"
version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
{file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
@@ -2411,13 +2419,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
[[package]]
name = "pytest-asyncio"
version = "0.23.2"
version = "0.23.3"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-asyncio-0.23.2.tar.gz", hash = "sha256:c16052382554c7b22d48782ab3438d5b10f8cf7a4bdcae7f0f67f097d95beecc"},
{file = "pytest_asyncio-0.23.2-py3-none-any.whl", hash = "sha256:ea9021364e32d58f0be43b91c6233fb8d2224ccef2398d6837559e587682808f"},
{file = "pytest-asyncio-0.23.3.tar.gz", hash = "sha256:af313ce900a62fbe2b1aed18e37ad757f1ef9940c6b6a88e2954de38d6b1fb9f"},
{file = "pytest_asyncio-0.23.3-py3-none-any.whl", hash = "sha256:37a9d912e8338ee7b4a3e917381d1c95bfc8682048cb0fbc35baba316ec1faba"},
]
[package.dependencies]
@@ -2759,28 +2767,28 @@ files = [
[[package]]
name = "ruff"
version = "0.1.9"
version = "0.1.11"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"},
{file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"},
{file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"},
{file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"},
{file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"},
{file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"},
{file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"},
{file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"},
{file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"},
{file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"},
{file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"},
{file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"},
{file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"},
{file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"},
{file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"},
{file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"},
{file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"},
{file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"},
{file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"},
{file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"},
{file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"},
{file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"},
]
[[package]]
@@ -2960,13 +2968,13 @@ files = [
[[package]]
name = "starlette"
version = "0.27.0"
version = "0.35.1"
description = "The little ASGI library that shines."
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"},
{file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"},
{file = "starlette-0.35.1-py3-none-any.whl", hash = "sha256:50bbbda9baa098e361f398fda0928062abbaf1f54f4fadcbe17c092a01eb9a25"},
{file = "starlette-0.35.1.tar.gz", hash = "sha256:3e2639dac3520e4f58734ed22553f950d3f3cb1001cd2eaac4d57e8cdc5f66bc"},
]
[package.dependencies]

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.92.1"
version = "1.93.2"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"
@@ -60,7 +60,6 @@ warn_untyped_fields = true
line-length = 120
target-version = "py311"
select = ["E", "F", "I"]
ignore = ["F401"]
[tool.ruff.per-file-ignores]
"test_main.py" = ["F403"]

View File

@@ -62,7 +62,7 @@ fi
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
npm --prefix server version $SERVER_PUMP
npm --prefix server run api:generate
make open-api
poetry --directory machine-learning version $SERVER_PUMP
fi

View File

@@ -1,4 +0,0 @@
{
"flutterSdkVersion": "3.13.0",
"flavors": {}
}

3
mobile/.fvmrc Normal file
View File

@@ -0,0 +1,3 @@
{
"flutter": "3.13.6"
}

6
mobile/.gitignore vendored
View File

@@ -31,7 +31,6 @@
.pub-cache/
.pub/
/build/
.fvm/flutter_sdk
# Web related
lib/generated_plugin_registrant.dart
@@ -53,4 +52,7 @@ ios/fastlane/report.xml
# Isar
default.isar
default.isar.lock
libisar.so
libisar.so
# FVM Version Cache
.fvm/

View File

@@ -1,10 +1,8 @@
{
"dart.flutterSdkPath": ".fvm/flutter_sdk",
// Remove .fvm files from search
"dart.flutterSdkPath": ".fvm\\versions\\3.13.6",
"search.exclude": {
"**/.fvm": true
},
// Remove from file watching
"files.watcherExclude": {
"**/.fvm": true
}

View File

@@ -3,7 +3,7 @@ buildscript {
ext.kotlin_coroutines_version = '1.7.1'
ext.work_version = '2.7.1'
ext.concurrent_version = '1.1.0'
ext.guava_version = '31.0.1-android'
ext.guava_version = '33.0.0-android'
ext.glide_version = '4.14.2'
repositories {
google()

View File

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

View File

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000217">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000253">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="66.694734">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="74.926686">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="27.6926">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="28.933555">
</testcase>

View File

@@ -141,7 +141,10 @@
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
"control_bottom_app_bar_archive": "Archive",
"control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_delete": "Delete",
"control_bottom_app_bar_delete_from_immich": "Delete from Immich",
"control_bottom_app_bar_trash_from_immich": "Move to Trash",
"control_bottom_app_bar_delete_from_local": "Delete from device",
"control_bottom_app_bar_delete": "Remove Everywhere",
"control_bottom_app_bar_edit_location": "Edit Location",
"control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Favorite",
@@ -162,8 +165,14 @@
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server",
"delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server",
"delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device",
"delete_dialog_cancel": "Cancel",
"delete_dialog_ok": "Delete",
"delete_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_force": "Delete Anyway",
"delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only",
"delete_dialog_title": "Delete Permanently",
"delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
"delete_shared_link_dialog_title": "Delete Shared Link",
@@ -190,6 +199,7 @@
"home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_building_timeline": "Building the timeline",
"home_page_delete_err_partner": "Can not delete partner assets, skipping",
"home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
@@ -253,7 +263,7 @@
"map_no_assets_in_bounds": "No photos in this area",
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
"map_no_location_permission_title": "Location Permission denied",
"map_settings_dark_mode": "Dark mode",
"map_settings_theme_settings": "Map Theme",
"map_settings_date_range_option_all": "All",
"map_settings_date_range_option_day": "Past 24 hours",
"map_settings_date_range_option_days": "Past {} days",
@@ -378,7 +388,6 @@
"share_dialog_preparing": "Preparing...",
"shared_link_app_bar_title": "Shared Links",
"shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_create_app_bar_title": "Create link to share",
"shared_link_create_error": "Error while creating shared link",
"shared_link_create_info": "Let anyone with the link see the selected photo(s)",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 23 KiB

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