mirror of
https://github.com/immich-app/immich.git
synced 2026-03-01 11:20:12 +03:00
Compare commits
161 Commits
postgres-s
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e06cedb626 | ||
|
|
ac5ef6a56d | ||
|
|
d6c724b13b | ||
|
|
aa87d1b9a3 | ||
|
|
dc4da4b3d6 | ||
|
|
7dbd08a747 | ||
|
|
1d89190f96 | ||
|
|
c2d8400899 | ||
|
|
a100a4025e | ||
|
|
334fc250d3 | ||
|
|
28ca5f59fe | ||
|
|
789d82632a | ||
|
|
9f9569c152 | ||
|
|
fae05270a3 | ||
|
|
771816f601 | ||
|
|
e25ec4ec17 | ||
|
|
dd9046508d | ||
|
|
177d1c9a30 | ||
|
|
ded8d4e2b4 | ||
|
|
e454c3566b | ||
|
|
4c79c3c902 | ||
|
|
3bed1b6131 | ||
|
|
3c9fb651d0 | ||
|
|
55e625a2ac | ||
|
|
ca6c486a80 | ||
|
|
d94d9600a7 | ||
|
|
11e5c42bc9 | ||
|
|
33c6cf8325 | ||
|
|
dd97395f3a | ||
|
|
7ae268e287 | ||
|
|
f07e2b58f0 | ||
|
|
4b8f90aa55 | ||
|
|
55ee9f76da | ||
|
|
30f6d4439e | ||
|
|
f62d98a0d1 | ||
|
|
db3d580761 | ||
|
|
0bc38fefe6 | ||
|
|
acc4219849 | ||
|
|
5234e21241 | ||
|
|
17b327bfcd | ||
|
|
d14d0a9b9b | ||
|
|
bf47147fbb | ||
|
|
9ea0a69a72 | ||
|
|
00f43ffc25 | ||
|
|
96dc4a77a0 | ||
|
|
db7158b967 | ||
|
|
e5722c525b | ||
|
|
f616de5af8 | ||
|
|
4f39663d27 | ||
|
|
367025a3a8 | ||
|
|
60dafecdc9 | ||
|
|
16c1c3c780 | ||
|
|
e633bc3f24 | ||
|
|
a07d7b0c82 | ||
|
|
a469d350be | ||
|
|
ccab4c88bb | ||
|
|
430638e129 | ||
|
|
caebe5166a | ||
|
|
1bd28c3e78 | ||
|
|
31a55aaa73 | ||
|
|
8b2e1509ff | ||
|
|
d0cb97f994 | ||
|
|
f0cf3311d5 | ||
|
|
3ce0654cab | ||
|
|
f0e2fced57 | ||
|
|
8ba20cbd44 | ||
|
|
1d25267f22 | ||
|
|
a4d95b7aba | ||
|
|
25d0bdc9f5 | ||
|
|
905b9bd560 | ||
|
|
672743f543 | ||
|
|
27c45b5ddb | ||
|
|
82c6302549 | ||
|
|
aae64b5e2f | ||
|
|
18bf96b4b2 | ||
|
|
84f2956941 | ||
|
|
6044b41648 | ||
|
|
b4e16efdf4 | ||
|
|
19da655390 | ||
|
|
a1839b3676 | ||
|
|
7461479f60 | ||
|
|
01050a3d54 | ||
|
|
e8bedfdb7a | ||
|
|
7b4cabc2c6 | ||
|
|
5c7c07a09f | ||
|
|
e6ac48f4b5 | ||
|
|
3d4dec0cca | ||
|
|
1d11106dd0 | ||
|
|
8eec3c810e | ||
|
|
a43680c8b1 | ||
|
|
b2a510efee | ||
|
|
a0077a0f51 | ||
|
|
aa02310d63 | ||
|
|
7394fa1491 | ||
|
|
99f7eb4ce6 | ||
|
|
ffd54d0431 | ||
|
|
7005e9fc50 | ||
|
|
4f2e6e3f15 | ||
|
|
8b5fc3d8bc | ||
|
|
0fa385c465 | ||
|
|
db4e7abf6d | ||
|
|
dadd20acfc | ||
|
|
f04efbb714 | ||
|
|
208c07af1f | ||
|
|
72a5ccaa53 | ||
|
|
fd0338f89c | ||
|
|
d0ed76dc37 | ||
|
|
e0bb5f70ec | ||
|
|
f965daa8d2 | ||
|
|
316f86d25e | ||
|
|
e520fc3b63 | ||
|
|
b3b9834c00 | ||
|
|
84f7fb63ee | ||
|
|
1f8359ead4 | ||
|
|
ea30c9d2ba | ||
|
|
d1abdea420 | ||
|
|
ae8dad68fc | ||
|
|
227ff70b6e | ||
|
|
ee7ac09450 | ||
|
|
2e59dbdc12 | ||
|
|
c4c7f94317 | ||
|
|
d004d7e21b | ||
|
|
5f95aab437 | ||
|
|
dd632f38de | ||
|
|
6f7fc94710 | ||
|
|
85cb515cae | ||
|
|
65e1bb83b7 | ||
|
|
d9b1b69827 | ||
|
|
b2050583f5 | ||
|
|
1bdc24c730 | ||
|
|
5adb75c272 | ||
|
|
8f9ea6a171 | ||
|
|
3f41916ad7 | ||
|
|
5c6433b4ca | ||
|
|
06d487782e | ||
|
|
455afbb119 | ||
|
|
0767ae0c8a | ||
|
|
a16a00ebd4 | ||
|
|
398b750ef7 | ||
|
|
18bbb5b4db | ||
|
|
b3c37905f7 | ||
|
|
90ef6c4e28 | ||
|
|
ceef65154d | ||
|
|
de7b42eb23 | ||
|
|
75bdd6a644 | ||
|
|
0da74569f2 | ||
|
|
cc9c261fd0 | ||
|
|
4dccc2082b | ||
|
|
9211013996 | ||
|
|
156e3479fa | ||
|
|
19ef196150 | ||
|
|
d2682f160e | ||
|
|
c9dd8e0a79 | ||
|
|
f6e10afe2b | ||
|
|
5f87047490 | ||
|
|
75e3b0467a | ||
|
|
df4c25e567 | ||
|
|
ff7dca35f5 | ||
|
|
49ba833e4c | ||
|
|
9ab887d5d2 | ||
|
|
d264e78d3f |
@@ -2,6 +2,7 @@
|
|||||||
"name": "Immich - Backend, Frontend and ML",
|
"name": "Immich - Backend, Frontend and ML",
|
||||||
"service": "immich-server",
|
"service": "immich-server",
|
||||||
"runServices": [
|
"runServices": [
|
||||||
|
"immich-init",
|
||||||
"immich-server",
|
"immich-server",
|
||||||
"redis",
|
"redis",
|
||||||
"database",
|
"database",
|
||||||
@@ -31,29 +32,8 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
|
||||||
"label": "Fix Permissions, Install Dependencies",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "[ -f /immich-devcontainer/container-start.sh ] && /immich-devcontainer/container-start.sh || exit 0",
|
|
||||||
"isBackground": true,
|
|
||||||
"presentation": {
|
|
||||||
"echo": true,
|
|
||||||
"reveal": "always",
|
|
||||||
"focus": false,
|
|
||||||
"panel": "dedicated",
|
|
||||||
"showReuseMessage": true,
|
|
||||||
"clear": false,
|
|
||||||
"group": "Devcontainer tasks",
|
|
||||||
"close": true
|
|
||||||
},
|
|
||||||
"runOptions": {
|
|
||||||
"runOn": "default"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "Immich API Server (Nest)",
|
"label": "Immich API Server (Nest)",
|
||||||
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
|
"command": "[ -f /immich-devcontainer/container-start-backend.sh ] && /immich-devcontainer/container-start-backend.sh || exit 0",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
@@ -74,7 +54,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Immich Web Server (Vite)",
|
"label": "Immich Web Server (Vite)",
|
||||||
"dependsOn": ["Fix Permissions, Install Dependencies"],
|
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
|
"command": "[ -f /immich-devcontainer/container-start-frontend.sh ] && /immich-devcontainer/container-start-frontend.sh || exit 0",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
@@ -130,8 +109,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"overrideCommand": true,
|
"overrideCommand": true,
|
||||||
"workspaceFolder": "/workspaces/immich",
|
"workspaceFolder": "/usr/src/app",
|
||||||
"remoteUser": "node",
|
"remoteUser": "root",
|
||||||
"userEnvProbe": "loginInteractiveShell",
|
"userEnvProbe": "loginInteractiveShell",
|
||||||
"remoteEnv": {
|
"remoteEnv": {
|
||||||
// The location where your uploaded files are stored
|
// The location where your uploaded files are stored
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
services:
|
services:
|
||||||
|
immich-app-base:
|
||||||
|
image: busybox
|
||||||
immich-server:
|
immich-server:
|
||||||
|
extends:
|
||||||
|
service: immich-app-base
|
||||||
|
profiles: !reset []
|
||||||
|
image: immich-server-dev:latest
|
||||||
build:
|
build:
|
||||||
target: dev-container-mobile
|
target: dev-container-mobile
|
||||||
environment:
|
environment:
|
||||||
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
||||||
volumes: !override # bind mount host to /workspaces/immich
|
volumes:
|
||||||
- ..:/workspaces/immich
|
|
||||||
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||||
- pnpm-store:/usr/src/app/.pnpm-store
|
|
||||||
- server-node_modules:/usr/src/app/server/node_modules
|
|
||||||
- web-node_modules:/usr/src/app/web/node_modules
|
|
||||||
- github-node_modules:/usr/src/app/.github/node_modules
|
|
||||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
|
||||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
|
||||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
|
||||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
|
||||||
- app-node_modules:/usr/src/app/node_modules
|
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
|
||||||
- coverage:/usr/src/app/web/coverage
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
immich-web:
|
immich-web:
|
||||||
env_file: !reset []
|
env_file: !reset []
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"name": "Immich - Mobile",
|
"name": "Immich - Mobile",
|
||||||
"service": "immich-server",
|
"service": "immich-server",
|
||||||
"runServices": [
|
"runServices": [
|
||||||
|
"immich-init",
|
||||||
"immich-server",
|
"immich-server",
|
||||||
"redis",
|
"redis",
|
||||||
"database",
|
"database",
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
},
|
},
|
||||||
"forwardPorts": [],
|
"forwardPorts": [],
|
||||||
"overrideCommand": true,
|
"overrideCommand": true,
|
||||||
"workspaceFolder": "/workspaces/immich",
|
"workspaceFolder": "/usr/src/app",
|
||||||
"remoteUser": "node",
|
"remoteUser": "node",
|
||||||
"userEnvProbe": "loginInteractiveShell",
|
"userEnvProbe": "loginInteractiveShell",
|
||||||
"remoteEnv": {
|
"remoteEnv": {
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
export IMMICH_PORT="${DEV_SERVER_PORT:-2283}"
|
export IMMICH_PORT="${DEV_SERVER_PORT:-2283}"
|
||||||
export DEV_PORT="${DEV_PORT:-3000}"
|
export DEV_PORT="${DEV_PORT:-3000}"
|
||||||
|
|
||||||
# search for immich directory inside workspace.
|
|
||||||
# /workspaces/immich is the bind mount, but other directories can be mounted if runing
|
|
||||||
# Devcontainer: Clone [repository|pull request] in container volumne
|
|
||||||
WORKSPACES_DIR="/workspaces"
|
|
||||||
IMMICH_DIR="$WORKSPACES_DIR/immich"
|
|
||||||
IMMICH_DEVCONTAINER_LOG="$HOME/immich-devcontainer.log"
|
IMMICH_DEVCONTAINER_LOG="$HOME/immich-devcontainer.log"
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
@@ -30,52 +25,8 @@ run_cmd() {
|
|||||||
return "${PIPESTATUS[0]}"
|
return "${PIPESTATUS[0]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find directories excluding /workspaces/immich
|
export IMMICH_WORKSPACE="/usr/src/app"
|
||||||
mapfile -t other_dirs < <(find "$WORKSPACES_DIR" -mindepth 1 -maxdepth 1 -type d ! -path "$IMMICH_DIR" ! -name ".*")
|
|
||||||
|
|
||||||
if [ ${#other_dirs[@]} -gt 1 ]; then
|
|
||||||
log "Error: More than one directory found in $WORKSPACES_DIR other than $IMMICH_DIR."
|
|
||||||
exit 1
|
|
||||||
elif [ ${#other_dirs[@]} -eq 1 ]; then
|
|
||||||
export IMMICH_WORKSPACE="${other_dirs[0]}"
|
|
||||||
else
|
|
||||||
export IMMICH_WORKSPACE="$IMMICH_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Found immich workspace in $IMMICH_WORKSPACE"
|
log "Found immich workspace in $IMMICH_WORKSPACE"
|
||||||
log ""
|
log ""
|
||||||
|
|
||||||
fix_permissions() {
|
|
||||||
|
|
||||||
log "Fixing permissions for ${IMMICH_WORKSPACE}"
|
|
||||||
|
|
||||||
# Change ownership for directories that exist
|
|
||||||
for dir in "${IMMICH_WORKSPACE}/.vscode" \
|
|
||||||
"${IMMICH_WORKSPACE}/server/upload" \
|
|
||||||
"${IMMICH_WORKSPACE}/.pnpm-store" \
|
|
||||||
"${IMMICH_WORKSPACE}/.github/node_modules" \
|
|
||||||
"${IMMICH_WORKSPACE}/cli/node_modules" \
|
|
||||||
"${IMMICH_WORKSPACE}/e2e/node_modules" \
|
|
||||||
"${IMMICH_WORKSPACE}/open-api/typescript-sdk/node_modules" \
|
|
||||||
"${IMMICH_WORKSPACE}/server/node_modules" \
|
|
||||||
"${IMMICH_WORKSPACE}/server/dist" \
|
|
||||||
"${IMMICH_WORKSPACE}/web/node_modules" \
|
|
||||||
"${IMMICH_WORKSPACE}/web/dist"; do
|
|
||||||
if [ -d "$dir" ]; then
|
|
||||||
run_cmd sudo chown node -R "$dir"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
log ""
|
|
||||||
}
|
|
||||||
|
|
||||||
install_dependencies() {
|
|
||||||
|
|
||||||
log "Installing dependencies"
|
|
||||||
(
|
|
||||||
cd "${IMMICH_WORKSPACE}" || exit 1
|
|
||||||
export CI=1 FROZEN=1 OFFLINE=1
|
|
||||||
run_cmd make setup-web-dev setup-server-dev
|
|
||||||
)
|
|
||||||
log ""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
services:
|
services:
|
||||||
|
immich-app-base:
|
||||||
|
image: busybox
|
||||||
immich-server:
|
immich-server:
|
||||||
|
extends:
|
||||||
|
service: immich-app-base
|
||||||
|
profiles: !reset []
|
||||||
|
image: immich-server-dev:latest
|
||||||
build:
|
build:
|
||||||
target: dev-container-server
|
target: dev-container-server
|
||||||
env_file: !reset []
|
env_file: !reset []
|
||||||
hostname: immich-dev
|
hostname: immich-dev
|
||||||
environment:
|
environment:
|
||||||
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
- IMMICH_SERVER_URL=http://127.0.0.1:2283/
|
||||||
volumes: !override
|
volumes:
|
||||||
- ..:/workspaces/immich
|
|
||||||
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- pnpm-store:/usr/src/app/.pnpm-store
|
- pnpm_store_server:/buildcache/pnpm-store
|
||||||
- server-node_modules:/usr/src/app/server/node_modules
|
|
||||||
- web-node_modules:/usr/src/app/web/node_modules
|
|
||||||
- github-node_modules:/usr/src/app/.github/node_modules
|
|
||||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
|
||||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
|
||||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
|
||||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
|
||||||
- app-node_modules:/usr/src/app/node_modules
|
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
|
||||||
- coverage:/usr/src/app/web/coverage
|
|
||||||
- ../plugins:/build/corePlugin
|
- ../plugins:/build/corePlugin
|
||||||
immich-web:
|
immich-web:
|
||||||
env_file: !reset []
|
env_file: !reset []
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# shellcheck source=common.sh
|
|
||||||
# shellcheck disable=SC1091
|
|
||||||
source /immich-devcontainer/container-common.sh
|
|
||||||
|
|
||||||
log "Setting up Immich dev container..."
|
|
||||||
fix_permissions
|
|
||||||
|
|
||||||
log "Setup complete, please wait while backend and frontend services automatically start"
|
|
||||||
log
|
|
||||||
log "If necessary, the services may be manually started using"
|
|
||||||
log
|
|
||||||
log "$ /immich-devcontainer/container-start-backend.sh"
|
|
||||||
log "$ /immich-devcontainer/container-start-frontend.sh"
|
|
||||||
log
|
|
||||||
log "From different terminal windows, as these scripts automatically restart the server"
|
|
||||||
log "on error, and will continuously run in a loop"
|
|
||||||
2
.github/.nvmrc
vendored
2
.github/.nvmrc
vendored
@@ -1 +1 @@
|
|||||||
24.13.0
|
24.13.1
|
||||||
|
|||||||
16
.github/workflows/build-mobile.yml
vendored
16
.github/workflows/build-mobile.yml
vendored
@@ -51,14 +51,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -79,12 +79,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -96,14 +96,14 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
||||||
|
|
||||||
- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
|
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Restore Gradle Cache
|
- name: Restore Gradle Cache
|
||||||
id: cache-gradle-restore
|
id: cache-gradle-restore
|
||||||
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
id: cache-gradle-save
|
||||||
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -185,7 +185,7 @@ jobs:
|
|||||||
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|||||||
4
.github/workflows/cache-cleanup.yml
vendored
4
.github/workflows/cache-cleanup.yml
vendored
@@ -19,13 +19,13 @@ jobs:
|
|||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
32
.github/workflows/check-openapi.yml
vendored
Normal file
32
.github/workflows/check-openapi.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Check OpenAPI
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'open-api/**'
|
||||||
|
- '.github/workflows/check-openapi.yml'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-openapi:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Check for breaking API changes
|
||||||
|
# sha is pinning to a commit instead of a tag since the action does not tag versions
|
||||||
|
uses: oasdiff/oasdiff-action/breaking@ccb863950ce437a50f8f1a40d2a1112117e06ce4
|
||||||
|
with:
|
||||||
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
|
revision: open-api/immich-openapi-specs.json
|
||||||
|
fail-on: ERR
|
||||||
14
.github/workflows/cli.yml
vendored
14
.github/workflows/cli.yml
vendored
@@ -31,12 +31,12 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
@@ -71,13 +71,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||||
with:
|
with:
|
||||||
file: cli/Dockerfile
|
file: cli/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
2
.github/workflows/close-duplicates.yml
vendored
2
.github/workflows/close-duplicates.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
needs: [get_body, should_run]
|
needs: [get_body, should_run]
|
||||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/immich-app/mdq:main@sha256:ab9f163cd5d5cec42704a26ca2769ecf3f10aa8e7bae847f1d527cdf075946e6
|
image: ghcr.io/immich-app/mdq:main@sha256:4f9860d04c88f7f87861f8ee84bfeedaec15ed7ca5ca87bc7db44b036f81645f
|
||||||
outputs:
|
outputs:
|
||||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
4
.github/workflows/close-llm-pr.yml
vendored
4
.github/workflows/close-llm-pr.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: Close LLM-generated PRs
|
name: Close LLM-generated PRs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
gh api graphql \
|
gh api graphql \
|
||||||
-f prId="$NODE_ID" \
|
-f prId="$NODE_ID" \
|
||||||
-f body="Thank you for your interest in contributing to Immich! Unfortunately this PR looks like it was generated using an LLM. As noted in our CONTRIBUTING.md, we request that you don't use LLMs to generate PRs as those are not a good use of maintainer time." \
|
-f body="Thank you for your interest in contributing to Immich! Unfortunately this PR looks like it was generated using an LLM. As noted in our [CONTRIBUTING.md](https://github.com/immich-app/immich/blob/main/CONTRIBUTING.md#use-of-generative-ai), we request that you don't use LLMs to generate PRs as those are not a good use of maintainer time." \
|
||||||
-f query='
|
-f query='
|
||||||
mutation CommentAndClosePR($prId: ID!, $body: String!) {
|
mutation CommentAndClosePR($prId: ID!, $body: String!) {
|
||||||
addComment(input: {
|
addComment(input: {
|
||||||
|
|||||||
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
@@ -44,20 +44,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -83,6 +83,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
12
.github/workflows/docker.yml
vendored
12
.github/workflows/docker.yml
vendored
@@ -23,14 +23,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
suffix: ['']
|
suffix: ['']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -132,7 +132,7 @@ jobs:
|
|||||||
suffixes: '-rocm'
|
suffixes: '-rocm'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
runner-mapping: '{"linux/amd64": "pokedex-giant"}'
|
runner-mapping: '{"linux/amd64": "pokedex-giant"}'
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@bd49ed7a5a6022149f79b6564df48177476a822b # multi-runner-build-workflow-v2.2.1
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
name: Build and Push Server
|
name: Build and Push Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@0477486d82313fba68f7c82c034120a4b8981297 # multi-runner-build-workflow-v2.1.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@bd49ed7a5a6022149f79b6564df48177476a822b # multi-runner-build-workflow-v2.2.1
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|||||||
10
.github/workflows/docs-build.yml
vendored
10
.github/workflows/docs-build.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -54,13 +54,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './docs/.nvmrc'
|
node-version-file: './docs/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
25
.github/workflows/docs-deploy.yml
vendored
25
.github/workflows/docs-deploy.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
artifact: ${{ steps.get-artifact.outputs.result }}
|
artifact: ${{ steps.get-artifact.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -119,19 +119,19 @@ jobs:
|
|||||||
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
uses: immich-app/devtools/actions/use-mise@dab18118da6476e8237ac94080fd937983fecd42 # use-mise-action-v1.1.2
|
||||||
|
|
||||||
- name: Load parameters
|
- name: Load parameters
|
||||||
id: parameters
|
id: parameters
|
||||||
@@ -192,16 +192,13 @@ jobs:
|
|||||||
' >> $GITHUB_OUTPUT
|
' >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Publish to Cloudflare Pages
|
- name: Publish to Cloudflare Pages
|
||||||
# TODO: Action is deprecated
|
working-directory: docs
|
||||||
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1.5.0
|
env:
|
||||||
with:
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
PROJECT_NAME: ${{ steps.docs-output.outputs.projectName }}
|
||||||
projectName: ${{ steps.docs-output.outputs.projectName }}
|
BRANCH_NAME: ${{ steps.parameters.outputs.name }}
|
||||||
workingDirectory: 'docs'
|
run: mise run //docs:deploy
|
||||||
directory: 'build'
|
|
||||||
branch: ${{ steps.parameters.outputs.name }}
|
|
||||||
wranglerVersion: '3'
|
|
||||||
|
|
||||||
- name: Deploy Docs Release Domain
|
- name: Deploy Docs Release Domain
|
||||||
if: ${{ steps.parameters.outputs.event == 'release' }}
|
if: ${{ steps.parameters.outputs.event == 'release' }}
|
||||||
|
|||||||
6
.github/workflows/docs-destroy.yml
vendored
6
.github/workflows/docs-destroy.yml
vendored
@@ -17,19 +17,19 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- name: Setup Mise
|
||||||
uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
|
uses: immich-app/devtools/actions/use-mise@dab18118da6476e8237ac94080fd937983fecd42 # use-mise-action-v1.1.2
|
||||||
|
|
||||||
- name: Destroy Docs Subdomain
|
- name: Destroy Docs Subdomain
|
||||||
env:
|
env:
|
||||||
|
|||||||
6
.github/workflows/fix-format.yml
vendored
6
.github/workflows/fix-format.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: 'Checkout'
|
- name: 'Checkout'
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
@@ -32,14 +32,14 @@ jobs:
|
|||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
|
||||||
- name: Fix formatting
|
- name: Fix formatting
|
||||||
run: pnpm --recursive install && pnpm run --recursive --parallel fix:format
|
run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix
|
||||||
|
|
||||||
- name: Commit and push
|
- name: Commit and push
|
||||||
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
|
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
|
||||||
|
|||||||
2
.github/workflows/pr-label-validation.yml
vendored
2
.github/workflows/pr-label-validation.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
2
.github/workflows/pr-labeler.yml
vendored
2
.github/workflows/pr-labeler.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
8
.github/workflows/prepare-release.yml
vendored
8
.github/workflows/prepare-release.yml
vendored
@@ -56,20 +56,20 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|||||||
4
.github/workflows/preview-label.yaml
vendored
4
.github/workflows/preview-label.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
8
.github/workflows/release-pr.yml
vendored
8
.github/workflows/release-pr.yml
vendored
@@ -23,20 +23,20 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -159,7 +159,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create PR
|
- name: Create PR
|
||||||
id: create-pr
|
id: create-pr
|
||||||
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}'
|
commit-message: 'chore: release ${{ steps.bump-type.outputs.next }}'
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -88,6 +88,7 @@ jobs:
|
|||||||
draft: true
|
draft: true
|
||||||
files: |
|
files: |
|
||||||
docker/docker-compose.yml
|
docker/docker-compose.yml
|
||||||
|
docker/docker-compose.rootless.yml
|
||||||
docker/example.env
|
docker/example.env
|
||||||
docker/hwaccel.ml.yml
|
docker/hwaccel.ml.yml
|
||||||
docker/hwaccel.transcoding.yml
|
docker/hwaccel.transcoding.yml
|
||||||
|
|||||||
6
.github/workflows/sdk.yml
vendored
6
.github/workflows/sdk.yml
vendored
@@ -19,12 +19,12 @@ jobs:
|
|||||||
working-directory: ./open-api/typescript-sdk
|
working-directory: ./open-api/typescript-sdk
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
|
|
||||||
# Setup .npmrc file to publish to npm
|
# Setup .npmrc file to publish to npm
|
||||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
node-version-file: './open-api/typescript-sdk/.nvmrc'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|||||||
16
.github/workflows/static_analysis.yml
vendored
16
.github/workflows/static_analysis.yml
vendored
@@ -20,14 +20,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -49,13 +49,13 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -69,6 +69,14 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dart pub get
|
run: dart pub get
|
||||||
|
|
||||||
|
- name: Install dependencies for UI package
|
||||||
|
run: dart pub get
|
||||||
|
working-directory: ./mobile/packages/ui
|
||||||
|
|
||||||
|
- name: Install dependencies for UI Showcase
|
||||||
|
run: dart pub get
|
||||||
|
working-directory: ./mobile/packages/ui/showcase
|
||||||
|
|
||||||
- name: Install DCM
|
- name: Install DCM
|
||||||
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1
|
||||||
with:
|
with:
|
||||||
|
|||||||
130
.github/workflows/test.yml
vendored
130
.github/workflows/test.yml
vendored
@@ -17,14 +17,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -63,13 +63,13 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -108,20 +108,20 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -155,20 +155,20 @@ jobs:
|
|||||||
working-directory: ./cli
|
working-directory: ./cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './cli/.nvmrc'
|
node-version-file: './cli/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -197,20 +197,20 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -241,20 +241,20 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -279,20 +279,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './web/.nvmrc'
|
node-version-file: './web/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -327,20 +327,20 @@ jobs:
|
|||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -373,13 +373,13 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -387,7 +387,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -412,13 +412,13 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -426,7 +426,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -446,12 +446,29 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Docker build
|
- name: Start Docker Compose
|
||||||
run: docker compose build
|
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run e2e tests (api & cli)
|
- name: Run e2e tests (api & cli)
|
||||||
|
env:
|
||||||
|
VITEST_DISABLE_DOCKER_SETUP: true
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Run e2e tests (maintenance)
|
||||||
|
env:
|
||||||
|
VITEST_DISABLE_DOCKER_SETUP: true
|
||||||
|
run: pnpm test:maintenance
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
- name: Capture Docker logs
|
||||||
|
if: always()
|
||||||
|
run: docker compose logs --no-color > docker-compose-logs.txt
|
||||||
|
working-directory: ./e2e
|
||||||
|
- name: Archive Docker logs
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: e2e-server-docker-logs-${{ matrix.runner }}
|
||||||
|
path: e2e/docker-compose-logs.txt
|
||||||
e2e-tests-web:
|
e2e-tests-web:
|
||||||
name: End-to-End Tests (Web)
|
name: End-to-End Tests (Web)
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -467,13 +484,13 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
@@ -481,7 +498,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './e2e/.nvmrc'
|
node-version-file: './e2e/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -494,16 +511,15 @@ jobs:
|
|||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: npx playwright install chromium --only-shell
|
run: pnpm exec playwright install chromium --only-shell
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Run e2e tests (web)
|
- name: Run e2e tests (web)
|
||||||
env:
|
env:
|
||||||
CI: true
|
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: npx playwright test --project=web
|
run: pnpm test:web
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Archive e2e test (web) results
|
- name: Archive e2e test (web) results
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
@@ -513,9 +529,8 @@ jobs:
|
|||||||
path: e2e/playwright-report/
|
path: e2e/playwright-report/
|
||||||
- name: Run ui tests (web)
|
- name: Run ui tests (web)
|
||||||
env:
|
env:
|
||||||
CI: true
|
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: npx playwright test --project=ui
|
run: pnpm test:web:ui
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Archive ui test (web) results
|
- name: Archive ui test (web) results
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
@@ -525,9 +540,8 @@ jobs:
|
|||||||
path: e2e/playwright-report/
|
path: e2e/playwright-report/
|
||||||
- name: Run maintenance tests
|
- name: Run maintenance tests
|
||||||
env:
|
env:
|
||||||
CI: true
|
|
||||||
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
PLAYWRIGHT_DISABLE_WEBSERVER: true
|
||||||
run: npx playwright test --project=maintenance
|
run: pnpm test:web:maintenance
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
- name: Archive maintenance tests (web) results
|
- name: Archive maintenance tests (web) results
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
@@ -543,7 +557,7 @@ jobs:
|
|||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: docker-compose-logs-${{ matrix.runner }}
|
name: e2e-web-docker-logs-${{ matrix.runner }}
|
||||||
path: e2e/docker-compose-logs.txt
|
path: e2e/docker-compose-logs.txt
|
||||||
success-check-e2e:
|
success-check-e2e:
|
||||||
name: End-to-End Tests Success
|
name: End-to-End Tests Success
|
||||||
@@ -564,12 +578,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -596,17 +610,17 @@ jobs:
|
|||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -636,20 +650,20 @@ jobs:
|
|||||||
working-directory: ./.github
|
working-directory: ./.github
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './.github/.nvmrc'
|
node-version-file: './.github/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -666,12 +680,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -687,20 +701,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -749,20 +763,20 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|||||||
6
.github/workflows/weblate-lock.yml
vendored
6
.github/workflows/weblate-lock.yml
vendored
@@ -24,14 +24,14 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@08bac802a312fc89808e0dd589271ca0974087b5 # pre-job-action-v2.0.0
|
uses: immich-app/devtools/actions/pre-job@eed0f8b8165ffcb951f2ba854b2dd031935e1d73 # pre-job-action-v2.0.2
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@da177fa133657503ddb7503f8ba53dccefec5da1 # create-workflow-token-action-v1.0.0
|
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
@@ -4,12 +4,18 @@ module.exports = {
|
|||||||
if (!pkg.name) {
|
if (!pkg.name) {
|
||||||
return pkg;
|
return pkg;
|
||||||
}
|
}
|
||||||
|
// make exiftool-vendored.pl a regular dependency since Docker prod
|
||||||
|
// images build with --no-optional to reduce image size
|
||||||
if (pkg.name === "exiftool-vendored") {
|
if (pkg.name === "exiftool-vendored") {
|
||||||
if (pkg.optionalDependencies["exiftool-vendored.pl"]) {
|
const binaryPackage =
|
||||||
// make exiftool-vendored.pl a regular dependency
|
process.platform === "win32"
|
||||||
pkg.dependencies["exiftool-vendored.pl"] =
|
? "exiftool-vendored.exe"
|
||||||
pkg.optionalDependencies["exiftool-vendored.pl"];
|
: "exiftool-vendored.pl";
|
||||||
delete pkg.optionalDependencies["exiftool-vendored.pl"];
|
|
||||||
|
if (pkg.optionalDependencies[binaryPackage]) {
|
||||||
|
pkg.dependencies[binaryPackage] =
|
||||||
|
pkg.optionalDependencies[binaryPackage];
|
||||||
|
delete pkg.optionalDependencies[binaryPackage];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pkg;
|
return pkg;
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -52,7 +52,7 @@ attach-server:
|
|||||||
docker exec -it docker_immich-server_1 sh
|
docker exec -it docker_immich-server_1 sh
|
||||||
|
|
||||||
renovate:
|
renovate:
|
||||||
LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
|
LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset
|
||||||
|
|
||||||
# Directories that need to be created for volumes or build output
|
# Directories that need to be created for volumes or build output
|
||||||
VOLUME_DIRS = \
|
VOLUME_DIRS = \
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24.13.0
|
24.13.1
|
||||||
|
|||||||
@@ -13,23 +13,23 @@
|
|||||||
"cli"
|
"cli"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.8.0",
|
"@eslint/js": "^10.0.0",
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "workspace:*",
|
||||||
"@types/byte-size": "^8.1.0",
|
"@types/byte-size": "^8.1.0",
|
||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/micromatch": "^4.0.9",
|
"@types/micromatch": "^4.0.9",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^24.10.11",
|
"@types/node": "^24.10.13",
|
||||||
"@vitest/coverage-v8": "^3.0.0",
|
"@vitest/coverage-v8": "^3.0.0",
|
||||||
"byte-size": "^9.0.0",
|
"byte-size": "^9.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"commander": "^12.0.0",
|
"commander": "^12.0.0",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^10.0.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^62.0.0",
|
"eslint-plugin-unicorn": "^63.0.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^17.0.0",
|
||||||
"mock-fs": "^5.2.0",
|
"mock-fs": "^5.2.0",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"prettier-plugin-organize-imports": "^4.0.0",
|
"prettier-plugin-organize-imports": "^4.0.0",
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:dev": "vite build --sourcemap true",
|
"build:dev": "vite build --sourcemap true",
|
||||||
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "pnpm run lint --fix",
|
||||||
"prepack": "npm run build",
|
"prepack": "pnpm run build",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:cov": "vitest --coverage",
|
"test:cov": "vitest --coverage",
|
||||||
"format": "prettier --check .",
|
"format": "prettier --check .",
|
||||||
@@ -69,6 +69,6 @@
|
|||||||
"micromatch": "^4.0.8"
|
"micromatch": "^4.0.8"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.13.0"
|
"node": "24.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ import { describe, expect, it, MockedFunction, vi } from 'vitest';
|
|||||||
import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk';
|
import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk';
|
||||||
import createFetchMock from 'vitest-fetch-mock';
|
import createFetchMock from 'vitest-fetch-mock';
|
||||||
|
|
||||||
import { checkForDuplicates, getAlbumName, startWatch, uploadFiles, UploadOptionsDto } from 'src/commands/asset';
|
import {
|
||||||
|
checkForDuplicates,
|
||||||
|
deleteFiles,
|
||||||
|
findSidecar,
|
||||||
|
getAlbumName,
|
||||||
|
startWatch,
|
||||||
|
uploadFiles,
|
||||||
|
UploadOptionsDto,
|
||||||
|
} from 'src/commands/asset';
|
||||||
|
|
||||||
vi.mock('@immich/sdk');
|
vi.mock('@immich/sdk');
|
||||||
|
|
||||||
@@ -309,3 +317,85 @@ describe('startWatch', () => {
|
|||||||
await fs.promises.rm(testFolder, { recursive: true, force: true });
|
await fs.promises.rm(testFolder, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('findSidecar', () => {
|
||||||
|
let testDir: string;
|
||||||
|
let testFilePath: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-sidecar-'));
|
||||||
|
testFilePath = path.join(testDir, 'test.jpg');
|
||||||
|
fs.writeFileSync(testFilePath, 'test');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(testDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find sidecar file with photo.xmp naming convention', () => {
|
||||||
|
const sidecarPath = path.join(testDir, 'test.xmp');
|
||||||
|
fs.writeFileSync(sidecarPath, 'xmp data');
|
||||||
|
|
||||||
|
const result = findSidecar(testFilePath);
|
||||||
|
expect(result).toBe(sidecarPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find sidecar file with photo.ext.xmp naming convention', () => {
|
||||||
|
const sidecarPath = path.join(testDir, 'test.jpg.xmp');
|
||||||
|
fs.writeFileSync(sidecarPath, 'xmp data');
|
||||||
|
|
||||||
|
const result = findSidecar(testFilePath);
|
||||||
|
expect(result).toBe(sidecarPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prefer photo.ext.xmp over photo.xmp when both exist', () => {
|
||||||
|
const sidecarPath1 = path.join(testDir, 'test.xmp');
|
||||||
|
const sidecarPath2 = path.join(testDir, 'test.jpg.xmp');
|
||||||
|
fs.writeFileSync(sidecarPath1, 'xmp data 1');
|
||||||
|
fs.writeFileSync(sidecarPath2, 'xmp data 2');
|
||||||
|
|
||||||
|
const result = findSidecar(testFilePath);
|
||||||
|
// Should return the first one found (photo.xmp) based on the order in the code
|
||||||
|
expect(result).toBe(sidecarPath1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when no sidecar file exists', () => {
|
||||||
|
const result = findSidecar(testFilePath);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteFiles', () => {
|
||||||
|
let testDir: string;
|
||||||
|
let testFilePath: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-delete-'));
|
||||||
|
testFilePath = path.join(testDir, 'test.jpg');
|
||||||
|
fs.writeFileSync(testFilePath, 'test');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(testDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete asset and sidecar file when main file is deleted', async () => {
|
||||||
|
const sidecarPath = path.join(testDir, 'test.xmp');
|
||||||
|
fs.writeFileSync(sidecarPath, 'xmp data');
|
||||||
|
|
||||||
|
await deleteFiles([{ id: 'test-id', filepath: testFilePath }], [], { delete: true, concurrency: 1 });
|
||||||
|
|
||||||
|
expect(fs.existsSync(testFilePath)).toBe(false);
|
||||||
|
expect(fs.existsSync(sidecarPath)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delete sidecar file when delete option is false', async () => {
|
||||||
|
const sidecarPath = path.join(testDir, 'test.xmp');
|
||||||
|
fs.writeFileSync(sidecarPath, 'xmp data');
|
||||||
|
|
||||||
|
await deleteFiles([{ id: 'test-id', filepath: testFilePath }], [], { delete: false, concurrency: 1 });
|
||||||
|
|
||||||
|
expect(fs.existsSync(testFilePath)).toBe(true);
|
||||||
|
expect(fs.existsSync(sidecarPath)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { Matcher, watch as watchFs } from 'chokidar';
|
|||||||
import { MultiBar, Presets, SingleBar } from 'cli-progress';
|
import { MultiBar, Presets, SingleBar } from 'cli-progress';
|
||||||
import { chunk } from 'lodash-es';
|
import { chunk } from 'lodash-es';
|
||||||
import micromatch from 'micromatch';
|
import micromatch from 'micromatch';
|
||||||
import { Stats, createReadStream } from 'node:fs';
|
import { Stats, createReadStream, existsSync } from 'node:fs';
|
||||||
import { stat, unlink } from 'node:fs/promises';
|
import { stat, unlink } from 'node:fs/promises';
|
||||||
import path, { basename } from 'node:path';
|
import path, { basename } from 'node:path';
|
||||||
import { Queue } from 'src/queue';
|
import { Queue } from 'src/queue';
|
||||||
@@ -403,23 +403,6 @@ export const uploadFiles = async (
|
|||||||
const uploadFile = async (input: string, stats: Stats): Promise<AssetMediaResponseDto> => {
|
const uploadFile = async (input: string, stats: Stats): Promise<AssetMediaResponseDto> => {
|
||||||
const { baseUrl, headers } = defaults;
|
const { baseUrl, headers } = defaults;
|
||||||
|
|
||||||
const assetPath = path.parse(input);
|
|
||||||
const noExtension = path.join(assetPath.dir, assetPath.name);
|
|
||||||
|
|
||||||
const sidecarsFiles = await Promise.all(
|
|
||||||
// XMP sidecars can come in two filename formats. For a photo named photo.ext, the filenames are photo.ext.xmp and photo.xmp
|
|
||||||
[`${noExtension}.xmp`, `${input}.xmp`].map(async (sidecarPath) => {
|
|
||||||
try {
|
|
||||||
const stats = await stat(sidecarPath);
|
|
||||||
return new UploadFile(sidecarPath, stats.size);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const sidecarData = sidecarsFiles.find((file): file is UploadFile => file !== false);
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('deviceAssetId', `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, ''));
|
formData.append('deviceAssetId', `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, ''));
|
||||||
formData.append('deviceId', 'CLI');
|
formData.append('deviceId', 'CLI');
|
||||||
@@ -429,8 +412,15 @@ const uploadFile = async (input: string, stats: Stats): Promise<AssetMediaRespon
|
|||||||
formData.append('isFavorite', 'false');
|
formData.append('isFavorite', 'false');
|
||||||
formData.append('assetData', new UploadFile(input, stats.size));
|
formData.append('assetData', new UploadFile(input, stats.size));
|
||||||
|
|
||||||
if (sidecarData) {
|
const sidecarPath = findSidecar(input);
|
||||||
formData.append('sidecarData', sidecarData);
|
if (sidecarPath) {
|
||||||
|
try {
|
||||||
|
const stats = await stat(sidecarPath);
|
||||||
|
const sidecarData = new UploadFile(sidecarPath, stats.size);
|
||||||
|
formData.append('sidecarData', sidecarData);
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/assets`, {
|
const response = await fetch(`${baseUrl}/assets`, {
|
||||||
@@ -446,7 +436,19 @@ const uploadFile = async (input: string, stats: Stats): Promise<AssetMediaRespon
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: UploadOptionsDto): Promise<void> => {
|
export const findSidecar = (filepath: string): string | undefined => {
|
||||||
|
const assetPath = path.parse(filepath);
|
||||||
|
const noExtension = path.join(assetPath.dir, assetPath.name);
|
||||||
|
|
||||||
|
// XMP sidecars can come in two filename formats. For a photo named photo.ext, the filenames are photo.ext.xmp and photo.xmp
|
||||||
|
for (const sidecarPath of [`${noExtension}.xmp`, `${filepath}.xmp`]) {
|
||||||
|
if (existsSync(sidecarPath)) {
|
||||||
|
return sidecarPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: UploadOptionsDto): Promise<void> => {
|
||||||
let fileCount = 0;
|
let fileCount = 0;
|
||||||
if (options.delete) {
|
if (options.delete) {
|
||||||
fileCount += uploaded.length;
|
fileCount += uploaded.length;
|
||||||
@@ -474,7 +476,15 @@ const deleteFiles = async (uploaded: Asset[], duplicates: Asset[], options: Uplo
|
|||||||
|
|
||||||
const chunkDelete = async (files: Asset[]) => {
|
const chunkDelete = async (files: Asset[]) => {
|
||||||
for (const assetBatch of chunk(files, options.concurrency)) {
|
for (const assetBatch of chunk(files, options.concurrency)) {
|
||||||
await Promise.all(assetBatch.map((input: Asset) => unlink(input.filepath)));
|
await Promise.all(
|
||||||
|
assetBatch.map(async (input: Asset) => {
|
||||||
|
await unlink(input.filepath);
|
||||||
|
const sidecarPath = findSidecar(input.filepath);
|
||||||
|
if (sidecarPath) {
|
||||||
|
await unlink(sidecarPath);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
deletionProgress.update(assetBatch.length);
|
deletionProgress.update(assetBatch.length);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,33 +14,65 @@
|
|||||||
name: immich-dev
|
name: immich-dev
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
immich-app-base:
|
||||||
|
profiles: ['_base']
|
||||||
|
tmpfs:
|
||||||
|
- /tmp
|
||||||
|
volumes:
|
||||||
|
- ..:/usr/src/app
|
||||||
|
- pnpm_cache:/buildcache/pnpm_cache
|
||||||
|
- server_node_modules:/usr/src/app/server/node_modules
|
||||||
|
- web_node_modules:/usr/src/app/web/node_modules
|
||||||
|
- github_node_modules:/usr/src/app/.github/node_modules
|
||||||
|
- cli_node_modules:/usr/src/app/cli/node_modules
|
||||||
|
- docs_node_modules:/usr/src/app/docs/node_modules
|
||||||
|
- e2e_node_modules:/usr/src/app/e2e/node_modules
|
||||||
|
- sdk_node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
||||||
|
- app_node_modules:/usr/src/app/node_modules
|
||||||
|
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||||
|
- coverage:/usr/src/app/web/coverage
|
||||||
|
|
||||||
|
immich-init:
|
||||||
|
extends:
|
||||||
|
service: immich-app-base
|
||||||
|
profiles: !reset []
|
||||||
|
container_name: immich_init
|
||||||
|
image: immich-server-dev:latest
|
||||||
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: server/Dockerfile.dev
|
||||||
|
target: dev
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
pnpm install
|
||||||
|
touch /tmp/init-complete
|
||||||
|
exec tail -f /dev/null
|
||||||
|
volumes:
|
||||||
|
- pnpm_store_server:/buildcache/pnpm-store
|
||||||
|
restart: 'no'
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'test', '-f', '/tmp/init-complete']
|
||||||
|
interval: 2s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 300
|
||||||
|
start_period: 300s
|
||||||
|
|
||||||
immich-server:
|
immich-server:
|
||||||
|
extends:
|
||||||
|
service: immich-app-base
|
||||||
|
profiles: !reset []
|
||||||
container_name: immich_server
|
container_name: immich_server
|
||||||
command: ['immich-dev']
|
command: ['immich-dev']
|
||||||
image: immich-server-dev:latest
|
image: immich-server-dev:latest
|
||||||
# extends:
|
|
||||||
# file: hwaccel.transcoding.yml
|
|
||||||
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
|
|
||||||
build:
|
build:
|
||||||
context: ../
|
context: ../
|
||||||
dockerfile: server/Dockerfile.dev
|
dockerfile: server/Dockerfile.dev
|
||||||
target: dev
|
target: dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/usr/src/app
|
|
||||||
- ${UPLOAD_LOCATION}/photos:/data
|
- ${UPLOAD_LOCATION}/photos:/data
|
||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
- pnpm-store:/usr/src/app/.pnpm-store
|
- pnpm_store_server:/buildcache/pnpm-store
|
||||||
- server-node_modules:/usr/src/app/server/node_modules
|
|
||||||
- web-node_modules:/usr/src/app/web/node_modules
|
|
||||||
- github-node_modules:/usr/src/app/.github/node_modules
|
|
||||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
|
||||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
|
||||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
|
||||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
|
||||||
- app-node_modules:/usr/src/app/node_modules
|
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
|
||||||
- coverage:/usr/src/app/web/coverage
|
|
||||||
- ../plugins:/build/corePlugin
|
- ../plugins:/build/corePlugin
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
@@ -63,6 +95,8 @@ services:
|
|||||||
- 9231:9231
|
- 9231:9231
|
||||||
- 2283:2283
|
- 2283:2283
|
||||||
depends_on:
|
depends_on:
|
||||||
|
immich-init:
|
||||||
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
database:
|
database:
|
||||||
@@ -71,6 +105,9 @@ services:
|
|||||||
disable: false
|
disable: false
|
||||||
|
|
||||||
immich-web:
|
immich-web:
|
||||||
|
extends:
|
||||||
|
service: immich-app-base
|
||||||
|
profiles: !reset []
|
||||||
container_name: immich_web
|
container_name: immich_web
|
||||||
image: immich-web-dev:latest
|
image: immich-web-dev:latest
|
||||||
build:
|
build:
|
||||||
@@ -84,20 +121,11 @@ services:
|
|||||||
- 3000:3000
|
- 3000:3000
|
||||||
- 24678:24678
|
- 24678:24678
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/usr/src/app
|
- pnpm_store_web:/buildcache/pnpm-store
|
||||||
- pnpm-store:/usr/src/app/.pnpm-store
|
|
||||||
- server-node_modules:/usr/src/app/server/node_modules
|
|
||||||
- web-node_modules:/usr/src/app/web/node_modules
|
|
||||||
- github-node_modules:/usr/src/app/.github/node_modules
|
|
||||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
|
||||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
|
||||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
|
||||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
|
||||||
- app-node_modules:/usr/src/app/node_modules
|
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
|
||||||
- coverage:/usr/src/app/web/coverage
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
|
immich-init:
|
||||||
|
condition: service_healthy
|
||||||
immich-server:
|
immich-server:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
|
||||||
@@ -116,7 +144,7 @@ services:
|
|||||||
- 3003:3003
|
- 3003:3003
|
||||||
volumes:
|
volumes:
|
||||||
- ../machine-learning/immich_ml:/usr/src/immich_ml
|
- ../machine-learning/immich_ml:/usr/src/immich_ml
|
||||||
- model-cache:/cache
|
- model_cache:/cache
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -156,7 +184,7 @@ services:
|
|||||||
# image: prom/prometheus
|
# image: prom/prometheus
|
||||||
# volumes:
|
# volumes:
|
||||||
# - ./prometheus.yml:/etc/prometheus/prometheus.yml
|
# - ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
# - prometheus-data:/prometheus
|
# - prometheus_data:/prometheus
|
||||||
|
|
||||||
# first login uses admin/admin
|
# first login uses admin/admin
|
||||||
# add data source for http://immich-prometheus:9090 to get started
|
# add data source for http://immich-prometheus:9090 to get started
|
||||||
@@ -167,20 +195,22 @@ services:
|
|||||||
# - 3000:3000
|
# - 3000:3000
|
||||||
# image: grafana/grafana:10.3.3-ubuntu
|
# image: grafana/grafana:10.3.3-ubuntu
|
||||||
# volumes:
|
# volumes:
|
||||||
# - grafana-data:/var/lib/grafana
|
# - grafana_data:/var/lib/grafana
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
model-cache:
|
model_cache:
|
||||||
prometheus-data:
|
prometheus_data:
|
||||||
grafana-data:
|
grafana_data:
|
||||||
pnpm-store:
|
pnpm_cache:
|
||||||
server-node_modules:
|
pnpm_store_server:
|
||||||
web-node_modules:
|
pnpm_store_web:
|
||||||
github-node_modules:
|
server_node_modules:
|
||||||
cli-node_modules:
|
web_node_modules:
|
||||||
docs-node_modules:
|
github_node_modules:
|
||||||
e2e-node_modules:
|
cli_node_modules:
|
||||||
sdk-node_modules:
|
docs_node_modules:
|
||||||
app-node_modules:
|
e2e_node_modules:
|
||||||
|
sdk_node_modules:
|
||||||
|
app_node_modules:
|
||||||
sveltekit:
|
sveltekit:
|
||||||
coverage:
|
coverage:
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24.13.0
|
24.13.1
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# OpenAPI
|
# API
|
||||||
|
|
||||||
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/).
|
Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generate API documentation. To view the published docs see [here](https://api.immich.app/).
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ Immich has three main clients:
|
|||||||
3. CLI - Command-line utility for bulk upload
|
3. CLI - Command-line utility for bulk upload
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
All three clients use [OpenAPI](./open-api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](./open-api.md).
|
All three clients use [OpenAPI](/api.md) to auto-generate rest clients for easy integration. For more information about this process, see [OpenAPI](/api.md).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Mobile App
|
### Mobile App
|
||||||
@@ -71,7 +71,7 @@ An incoming HTTP request is mapped to a controller (`src/controllers`). Controll
|
|||||||
|
|
||||||
### Domain Transfer Objects (DTOs)
|
### Domain Transfer Objects (DTOs)
|
||||||
|
|
||||||
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](./open-api.md) schemas and control the generated code used by each client.
|
The server uses [Domain Transfer Objects](https://en.wikipedia.org/wiki/Data_transfer_object) as public interfaces for the inputs (query, params, and body) and outputs (response) for each endpoint. DTOs translate to [OpenAPI](/api.md) schemas and control the generated code used by each client.
|
||||||
|
|
||||||
### Background Jobs
|
### Background Jobs
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ While this guide focuses on VS Code, you have many options for Dev Container dev
|
|||||||
**Self-Hostable Options:**
|
**Self-Hostable Options:**
|
||||||
|
|
||||||
- [Coder](https://coder.com) - Enterprise-focused, requires Terraform knowledge, self-managed
|
- [Coder](https://coder.com) - Enterprise-focused, requires Terraform knowledge, self-managed
|
||||||
- [DevPod](https://devpod.sh) - Client-only tool with excellent devcontainer.json support, works with any provider (local, cloud, or on-premise)
|
- [DevPod](https://devpod.sh) - Client-only tool with excellent devcontainer.json support, works with any provider (local, cloud, or on-premise). Check [quick-start guide](#quick-start-guide-for-devpod-with-docker)
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Dev Container Services
|
## Dev Container Services
|
||||||
@@ -408,7 +408,27 @@ If you encounter issues:
|
|||||||
1. Check container logs: View → Output → Select "Dev Containers"
|
1. Check container logs: View → Output → Select "Dev Containers"
|
||||||
2. Rebuild without cache: "Dev Containers: Rebuild Container Without Cache"
|
2. Rebuild without cache: "Dev Containers: Rebuild Container Without Cache"
|
||||||
3. Review [common Docker issues](https://docs.docker.com/desktop/troubleshoot/)
|
3. Review [common Docker issues](https://docs.docker.com/desktop/troubleshoot/)
|
||||||
4. Ask in [Discord](https://discord.immich.app) `#help-desk-support` channel
|
4. Ask in [Discord](https://discord.immich.app) `#contributing` channel
|
||||||
|
|
||||||
|
### Quick-start guide for DevPod with docker
|
||||||
|
|
||||||
|
You will need DevPod CLI (check [DevPod CLI installation guide](https://devpod.sh/docs/getting-started/install)) and Docker Desktop.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Step 1: Clone the Repository
|
||||||
|
git clone https://github.com/immich-app/immich.git
|
||||||
|
cd immich
|
||||||
|
|
||||||
|
# Step 2: Prepare DevPod (if you haven't already)
|
||||||
|
devpod provider add docker
|
||||||
|
devpod provider use docker
|
||||||
|
|
||||||
|
# Step 3: Build 'immich-server-dev' docker image first manually
|
||||||
|
docker build -f server/Dockerfile.dev -t immich-server-dev .
|
||||||
|
|
||||||
|
# Step 4: Now you can start devcontainer
|
||||||
|
devpod up .
|
||||||
|
```
|
||||||
|
|
||||||
## Mobile Development
|
## Mobile Development
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ You can use `dart fix --apply` and `dcm fix lib` to potentially correct some iss
|
|||||||
|
|
||||||
## OpenAPI
|
## OpenAPI
|
||||||
|
|
||||||
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/developer/open-api.md) for more details.
|
The OpenAPI client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file. Note that you should not modify this file directly as it is auto-generated. See [OpenAPI](/api.md) for more details.
|
||||||
|
|
||||||
## Database Migrations
|
## Database Migrations
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ There is an automatic scan job that is scheduled to run once a day. Its schedule
|
|||||||
|
|
||||||
This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
|
This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
|
||||||
|
|
||||||
|
### Deleting a Library
|
||||||
|
|
||||||
|
When deleting an external library, all assets inside are immediately deleted along with the library. Note that while a library can take a long time to fully delete in the background, it is immediately removed from the library list. If the deletion process is interrupted (for example, due to server restart), it will be cleaned up in the next nightly cron job. The cleanup process can also be manually initiated by clicking the "Scan All Libraries" button in the library list.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
|
Let's show a concrete example where we add an existing gallery to Immich. Here, we have the following folders we want to add:
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
|
|||||||
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
|
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
|
||||||
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
|
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
|
||||||
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
|
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting).
|
||||||
|
- MIGraphX is a new backend for AMD cards, which compiles models at runtime. As such, the first few inferences will be slow.
|
||||||
|
|
||||||
#### OpenVINO
|
#### OpenVINO
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a
|
|||||||
| `MP2T` | `.mts` `.m2ts` `.m2t` | :white_check_mark: | |
|
| `MP2T` | `.mts` `.m2ts` `.m2t` | :white_check_mark: | |
|
||||||
| `MP4` | `.mp4` `.insv` | :white_check_mark: | |
|
| `MP4` | `.mp4` `.insv` | :white_check_mark: | |
|
||||||
| `MPEG` | `.mpg` `.mpe` `.mpeg` | :white_check_mark: | |
|
| `MPEG` | `.mpg` `.mpe` `.mpeg` | :white_check_mark: | |
|
||||||
|
| `MXF` | `.mxf` | :white_check_mark: | |
|
||||||
| `QUICKTIME` | `.mov` | :white_check_mark: | |
|
| `QUICKTIME` | `.mov` | :white_check_mark: | |
|
||||||
| `WEBM` | `.webm` | :white_check_mark: | |
|
| `WEBM` | `.webm` | :white_check_mark: | |
|
||||||
| `WMV` | `.wmv` | :white_check_mark: | |
|
| `WMV` | `.wmv` | :white_check_mark: | |
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ A config file can be provided as an alternative to the UI configuration.
|
|||||||
|
|
||||||
### Step 1 - Create a new config file
|
### Step 1 - Create a new config file
|
||||||
|
|
||||||
In JSON format, create a new config file (e.g. `immich.json`) and put it in a location that can be accessed by Immich.
|
In JSON format, create a new config file (e.g. `immich.json`) and put it in a location mounted in the container that can be accessed by Immich.
|
||||||
|
YAML-formatted config files are also supported.
|
||||||
The default configuration looks like this:
|
The default configuration looks like this:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -251,6 +252,15 @@ So you can just grab it from there, paste it into a file and you're pretty much
|
|||||||
In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config.
|
In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config.
|
||||||
For more information, refer to the [Environment Variables](/install/environment-variables.md) section.
|
For more information, refer to the [Environment Variables](/install/environment-variables.md) section.
|
||||||
|
|
||||||
:::tip
|
:::info Docker Compose
|
||||||
YAML-formatted config files are also supported.
|
In your `.env` file, the variables `UPLOAD_LOCATION` and `DB_DATA_LOCATION` concern the location on the host.
|
||||||
:::
|
However, the variable `IMMICH_CONFIG_FILE` concerns the location inside the container, and informs the `immich-server` container that a configuration file is present.
|
||||||
|
|
||||||
|
It is recommended to reuse this variable in your `docker-compose.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
- ./configuration.yml:${IMMICH_CONFIG_FILE}
|
||||||
|
```
|
||||||
|
|
||||||
|
::
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ sidebar_position: 85
|
|||||||
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
|
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
|
||||||
|
|
||||||
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/).
|
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/).
|
||||||
|
|
||||||
**Please report app issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).**
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager.
|
Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const prism = require('prism-react-renderer');
|
|||||||
/** @type {import('@docusaurus/types').Config} */
|
/** @type {import('@docusaurus/types').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
title: 'Immich',
|
title: 'Immich',
|
||||||
tagline: 'High performance self-hosted photo and video backup solution directly from your mobile phone',
|
tagline: 'Self-hosted photo and video management solution',
|
||||||
url: 'https://docs.immich.app',
|
url: 'https://docs.immich.app',
|
||||||
baseUrl: '/',
|
baseUrl: '/',
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
@@ -93,35 +93,15 @@ const config = {
|
|||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/overview/quick-start',
|
href: 'https://immich.app/',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
label: 'Docs',
|
label: 'Home',
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://immich.app/roadmap',
|
|
||||||
position: 'right',
|
|
||||||
label: 'Roadmap',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://api.immich.app/',
|
|
||||||
position: 'right',
|
|
||||||
label: 'API',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://immich.store',
|
|
||||||
position: 'right',
|
|
||||||
label: 'Merch',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'https://github.com/immich-app/immich',
|
href: 'https://github.com/immich-app/immich',
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
href: 'https://discord.immich.app',
|
|
||||||
label: 'Discord',
|
|
||||||
position: 'right',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: 'html',
|
type: 'html',
|
||||||
position: 'right',
|
position: 'right',
|
||||||
@@ -134,19 +114,78 @@ const config = {
|
|||||||
style: 'light',
|
style: 'light',
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
title: 'Overview',
|
title: 'Download',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'Quick start',
|
label: 'Android',
|
||||||
to: '/overview/quick-start',
|
href: 'https://get.immich.app/android',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Installation',
|
label: 'iOS',
|
||||||
to: '/install/requirements',
|
href: 'https://get.immich.app/ios',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Contributing',
|
label: 'Server',
|
||||||
to: '/overview/support-the-project',
|
href: 'https://immich.app/download',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Company',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'FUTO',
|
||||||
|
href: 'https://futo.tech/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Purchase',
|
||||||
|
href: 'https://buy.immich.app/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Merch',
|
||||||
|
href: 'https://immich.store/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sites',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Home',
|
||||||
|
href: 'https://immich.app',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'My Immich',
|
||||||
|
href: 'https://my.immich.app/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Awesome Immich',
|
||||||
|
href: 'https://awesome.immich.app/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Immich API',
|
||||||
|
href: 'https://api.immich.app/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Immich Data',
|
||||||
|
href: 'https://data.immich.app/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Immich Datasets',
|
||||||
|
href: 'https://datasets.immich.app/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Miscellaneous',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Roadmap',
|
||||||
|
href: 'https://immich.app/roadmap',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cursed Knowledge',
|
||||||
|
href: 'https://immich.app/cursed-knowledge',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Privacy Policy',
|
label: 'Privacy Policy',
|
||||||
@@ -155,24 +194,7 @@ const config = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Documentation',
|
title: 'Social',
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'Roadmap',
|
|
||||||
href: 'https://immich.app/roadmap',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'API',
|
|
||||||
href: 'https://api.immich.app/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Cursed Knowledge',
|
|
||||||
href: 'https://immich.app/cursed-knowledge',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Links',
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'GitHub',
|
label: 'GitHub',
|
||||||
|
|||||||
@@ -23,3 +23,9 @@ run = "prettier --check ."
|
|||||||
[tasks."format-fix"]
|
[tasks."format-fix"]
|
||||||
env._.path = "./node_modules/.bin"
|
env._.path = "./node_modules/.bin"
|
||||||
run = "prettier --write ."
|
run = "prettier --write ."
|
||||||
|
|
||||||
|
[tasks.deploy]
|
||||||
|
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
||||||
|
|
||||||
|
[tools]
|
||||||
|
wrangler = "4.66.0"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"format:fix": "prettier --write .",
|
"format:fix": "prettier --write .",
|
||||||
"start": "docusaurus start --port 3005",
|
"start": "docusaurus start --port 3005",
|
||||||
"copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0",
|
"copy:openapi": "jq -c < ../open-api/immich-openapi-specs.json > ./static/openapi.json || exit 0",
|
||||||
"build": "npm run copy:openapi && docusaurus build",
|
"build": "pnpm run copy:openapi && docusaurus build",
|
||||||
"swizzle": "docusaurus swizzle",
|
"swizzle": "docusaurus swizzle",
|
||||||
"deploy": "docusaurus deploy",
|
"deploy": "docusaurus deploy",
|
||||||
"clear": "docusaurus clear",
|
"clear": "docusaurus clear",
|
||||||
@@ -58,6 +58,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.13.0"
|
"node": "24.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
docs/static/_redirects
vendored
1
docs/static/_redirects
vendored
@@ -23,6 +23,7 @@
|
|||||||
/features/storage-template /administration/storage-template 307
|
/features/storage-template /administration/storage-template 307
|
||||||
/features/user-management /administration/user-management 307
|
/features/user-management /administration/user-management 307
|
||||||
/developer/contributing /developer/pr-checklist 307
|
/developer/contributing /developer/pr-checklist 307
|
||||||
|
/developer/open-api /api 307
|
||||||
/guides/machine-learning /guides/remote-machine-learning 307
|
/guides/machine-learning /guides/remote-machine-learning 307
|
||||||
/administration/password-login /administration/system-settings 307
|
/administration/password-login /administration/system-settings 307
|
||||||
/features/search /features/searching 307
|
/features/search /features/searching 307
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24.13.0
|
24.13.1
|
||||||
|
|||||||
@@ -1,86 +1,77 @@
|
|||||||
name: immich-e2e
|
name: immich-e2e
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
immich-app-base:
|
||||||
|
extends:
|
||||||
|
file: ../docker/docker-compose.dev.yml
|
||||||
|
service: immich-app-base
|
||||||
|
|
||||||
|
immich-init:
|
||||||
|
extends:
|
||||||
|
file: ../docker/docker-compose.dev.yml
|
||||||
|
service: immich-init
|
||||||
|
container_name: immich-e2e-init
|
||||||
|
|
||||||
immich-server:
|
immich-server:
|
||||||
|
extends:
|
||||||
|
file: ../docker/docker-compose.dev.yml
|
||||||
|
service: immich-server
|
||||||
container_name: immich-e2e-server
|
container_name: immich-e2e-server
|
||||||
command: ['immich-dev']
|
ports: !reset []
|
||||||
image: immich-server-dev:latest
|
env_file: !reset []
|
||||||
build:
|
|
||||||
context: ../
|
|
||||||
dockerfile: server/Dockerfile.dev
|
|
||||||
target: dev
|
|
||||||
environment:
|
environment:
|
||||||
- DB_HOSTNAME=database
|
DB_HOSTNAME: database
|
||||||
- DB_USERNAME=postgres
|
DB_USERNAME: postgres
|
||||||
- DB_PASSWORD=postgres
|
DB_PASSWORD: postgres
|
||||||
- DB_DATABASE_NAME=immich
|
DB_DATABASE_NAME: immich
|
||||||
- IMMICH_MACHINE_LEARNING_ENABLED=false
|
IMMICH_MACHINE_LEARNING_ENABLED: 'false'
|
||||||
- IMMICH_TELEMETRY_INCLUDE=all
|
IMMICH_TELEMETRY_INCLUDE: all
|
||||||
- IMMICH_ENV=testing
|
IMMICH_ENV: testing
|
||||||
- IMMICH_PORT=2285
|
IMMICH_PORT: '2285'
|
||||||
- IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true
|
IMMICH_IGNORE_MOUNT_CHECK_ERRORS: 'true'
|
||||||
volumes:
|
volumes:
|
||||||
- ./test-assets:/test-assets
|
- ./test-assets:/test-assets
|
||||||
- ..:/usr/src/app
|
|
||||||
- ${UPLOAD_LOCATION}/photos:/data
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
- pnpm-store:/usr/src/app/.pnpm-store
|
|
||||||
- server-node_modules:/usr/src/app/server/node_modules
|
|
||||||
- web-node_modules:/usr/src/app/web/node_modules
|
|
||||||
- github-node_modules:/usr/src/app/.github/node_modules
|
|
||||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
|
||||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
|
||||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
|
||||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
|
||||||
- app-node_modules:/usr/src/app/node_modules
|
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
|
||||||
- coverage:/usr/src/app/web/coverage
|
|
||||||
- ../plugins:/build/corePlugin
|
|
||||||
depends_on:
|
depends_on:
|
||||||
|
immich-init:
|
||||||
|
condition: service_healthy
|
||||||
redis:
|
redis:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
database:
|
database:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
immich-web:
|
immich-web:
|
||||||
|
extends:
|
||||||
|
file: ../docker/docker-compose.dev.yml
|
||||||
|
service: immich-web
|
||||||
container_name: immich-e2e-web
|
container_name: immich-e2e-web
|
||||||
image: immich-web-dev:latest
|
ports: !override
|
||||||
build:
|
|
||||||
context: ../
|
|
||||||
dockerfile: server/Dockerfile.dev
|
|
||||||
target: dev
|
|
||||||
command: ['immich-web']
|
|
||||||
ports:
|
|
||||||
- 2285:3000
|
- 2285:3000
|
||||||
environment:
|
environment:
|
||||||
- IMMICH_SERVER_URL=http://immich-server:2285/
|
IMMICH_SERVER_URL: http://immich-server:2285/
|
||||||
volumes:
|
depends_on:
|
||||||
- ..:/usr/src/app
|
immich-init:
|
||||||
- pnpm-store:/usr/src/app/.pnpm-store
|
condition: service_healthy
|
||||||
- server-node_modules:/usr/src/app/server/node_modules
|
|
||||||
- web-node_modules:/usr/src/app/web/node_modules
|
|
||||||
- github-node_modules:/usr/src/app/.github/node_modules
|
|
||||||
- cli-node_modules:/usr/src/app/cli/node_modules
|
|
||||||
- docs-node_modules:/usr/src/app/docs/node_modules
|
|
||||||
- e2e-node_modules:/usr/src/app/e2e/node_modules
|
|
||||||
- sdk-node_modules:/usr/src/app/open-api/typescript-sdk/node_modules
|
|
||||||
- app-node_modules:/usr/src/app/node_modules
|
|
||||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
|
||||||
- coverage:/usr/src/app/web/coverage
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2-alpine@sha256:46884be93652d02a96a176ccf173d1040bef365c5706aa7b6a1931caec8bfeef
|
extends:
|
||||||
|
file: ../docker/docker-compose.dev.yml
|
||||||
|
service: redis
|
||||||
|
container_name: immich-e2e-redis
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338
|
extends:
|
||||||
|
file: ../docker/docker-compose.dev.yml
|
||||||
|
service: database
|
||||||
|
container_name: immich-e2e-postgres
|
||||||
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
||||||
|
env_file: !reset []
|
||||||
|
ports: !override
|
||||||
|
- 5435:5432
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: immich
|
POSTGRES_DB: immich
|
||||||
ports:
|
|
||||||
- 5435:5432
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD-SHELL', 'pg_isready -U postgres -d immich']
|
test: ['CMD-SHELL', 'pg_isready -U postgres -d immich']
|
||||||
interval: 1s
|
interval: 1s
|
||||||
@@ -89,17 +80,19 @@ services:
|
|||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
model-cache:
|
model_cache:
|
||||||
prometheus-data:
|
prometheus_data:
|
||||||
grafana-data:
|
grafana_data:
|
||||||
pnpm-store:
|
pnpm_cache:
|
||||||
server-node_modules:
|
pnpm_store_server:
|
||||||
web-node_modules:
|
pnpm_store_web:
|
||||||
github-node_modules:
|
server_node_modules:
|
||||||
cli-node_modules:
|
web_node_modules:
|
||||||
docs-node_modules:
|
github_node_modules:
|
||||||
e2e-node_modules:
|
cli_node_modules:
|
||||||
sdk-node_modules:
|
docs_node_modules:
|
||||||
app-node_modules:
|
e2e_node_modules:
|
||||||
|
sdk_node_modules:
|
||||||
|
app_node_modules:
|
||||||
sveltekit:
|
sveltekit:
|
||||||
coverage:
|
coverage:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ name: immich-e2e
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
e2e-auth-server:
|
e2e-auth-server:
|
||||||
|
container_name: immich-e2e-auth-server
|
||||||
build:
|
build:
|
||||||
context: ../e2e-auth-server
|
context: ../e2e-auth-server
|
||||||
ports:
|
ports:
|
||||||
@@ -22,15 +23,15 @@ services:
|
|||||||
- BUILD_SOURCE_REF=e2e
|
- BUILD_SOURCE_REF=e2e
|
||||||
- BUILD_SOURCE_COMMIT=e2eeeeeeeeeeeeeeeeee
|
- BUILD_SOURCE_COMMIT=e2eeeeeeeeeeeeeeeeee
|
||||||
environment:
|
environment:
|
||||||
- DB_HOSTNAME=database
|
DB_HOSTNAME: database
|
||||||
- DB_USERNAME=postgres
|
DB_USERNAME: postgres
|
||||||
- DB_PASSWORD=postgres
|
DB_PASSWORD: postgres
|
||||||
- DB_DATABASE_NAME=immich
|
DB_DATABASE_NAME: immich
|
||||||
- IMMICH_MACHINE_LEARNING_ENABLED=false
|
IMMICH_MACHINE_LEARNING_ENABLED: 'false'
|
||||||
- IMMICH_TELEMETRY_INCLUDE=all
|
IMMICH_TELEMETRY_INCLUDE: all
|
||||||
- IMMICH_ENV=testing
|
IMMICH_ENV: testing
|
||||||
- IMMICH_PORT=2285
|
IMMICH_PORT: '2285'
|
||||||
- IMMICH_IGNORE_MOUNT_CHECK_ERRORS=true
|
IMMICH_IGNORE_MOUNT_CHECK_ERRORS: 'true'
|
||||||
volumes:
|
volumes:
|
||||||
- ./test-assets:/test-assets
|
- ./test-assets:/test-assets
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -42,10 +43,14 @@ services:
|
|||||||
- 2285:2285
|
- 2285:2285
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2-alpine@sha256:46884be93652d02a96a176ccf173d1040bef365c5706aa7b6a1931caec8bfeef
|
container_name: immich-e2e-redis
|
||||||
|
image: docker.io/valkey/valkey:9@sha256:930b41430fb727f533c5982fe509b6f04233e26d0f7354e04de4b0d5c706e44e
|
||||||
|
healthcheck:
|
||||||
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.3.0@sha256:6f3e9d2c2177af16c2988ff71425d79d89ca630ec2f9c8db03209ab716542338
|
container_name: immich-e2e-postgres
|
||||||
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357191b76a916ae5eb93464d65c07511da41e3bf7a8416db519b40b1c23
|
||||||
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
command: -c fsync=off -c shared_preload_libraries=vchord.so -c config_file=/var/lib/postgresql/data/postgresql.conf
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|||||||
@@ -7,37 +7,42 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest --run",
|
"test": "vitest --run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"test:web": "npx playwright test",
|
"test:maintenance": "vitest --run --config vitest.maintenance.config.ts",
|
||||||
"start:web": "npx playwright test --ui",
|
"test:web": "pnpm exec playwright test --project=web",
|
||||||
|
"test:web:maintenance": "pnpm exec playwright test --project=maintenance",
|
||||||
|
"test:web:ui": "pnpm exec playwright test --project=ui",
|
||||||
|
"start:web": "pnpm exec playwright test --ui --project=web",
|
||||||
|
"start:web:maintenance": "pnpm exec playwright test --ui --project=maintenance",
|
||||||
|
"start:web:ui": "pnpm exec playwright test --ui --project=ui",
|
||||||
"format": "prettier --check .",
|
"format": "prettier --check .",
|
||||||
"format:fix": "prettier --write .",
|
"format:fix": "prettier --write .",
|
||||||
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "pnpm run lint --fix",
|
||||||
"check": "tsc --noEmit"
|
"check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.8.0",
|
"@eslint/js": "^10.0.0",
|
||||||
"@faker-js/faker": "^10.1.0",
|
"@faker-js/faker": "^10.1.0",
|
||||||
"@immich/cli": "file:../cli",
|
"@immich/cli": "workspace:*",
|
||||||
"@immich/e2e-auth-server": "file:../e2e-auth-server",
|
"@immich/e2e-auth-server": "workspace:*",
|
||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "workspace:*",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@socket.io/component-emitter": "^3.1.2",
|
"@socket.io/component-emitter": "^3.1.2",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^24.10.11",
|
"@types/node": "^24.10.13",
|
||||||
"@types/pg": "^8.15.1",
|
"@types/pg": "^8.15.1",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^10.0.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^62.0.0",
|
"eslint-plugin-unicorn": "^63.0.0",
|
||||||
"exiftool-vendored": "^34.3.0",
|
"exiftool-vendored": "^35.0.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^17.0.0",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
@@ -52,6 +57,6 @@
|
|||||||
"vitest": "^3.0.0"
|
"vitest": "^3.0.0"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.13.0"
|
"node": "24.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import dotenv from 'dotenv';
|
|||||||
import { cpus } from 'node:os';
|
import { cpus } from 'node:os';
|
||||||
import { resolve } from 'node:path';
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
dotenv.config({ path: resolve(import.meta.dirname, '.env') });
|
dotenv.config({ quiet: true, path: resolve(import.meta.dirname, '.env') });
|
||||||
|
|
||||||
export const playwrightHost = process.env.PLAYWRIGHT_HOST ?? '127.0.0.1';
|
export const playwrightHost = process.env.PLAYWRIGHT_HOST ?? '127.0.0.1';
|
||||||
export const playwrightDbHost = process.env.PLAYWRIGHT_DB_HOST ?? '127.0.0.1';
|
export const playwrightDbHost = process.env.PLAYWRIGHT_DB_HOST ?? '127.0.0.1';
|
||||||
@@ -48,7 +48,7 @@ const config: PlaywrightTestConfig = {
|
|||||||
{
|
{
|
||||||
name: 'maintenance',
|
name: 'maintenance',
|
||||||
use: { ...devices['Desktop Chrome'] },
|
use: { ...devices['Desktop Chrome'] },
|
||||||
testDir: './src/specs/maintenance',
|
testDir: './src/specs/maintenance/web',
|
||||||
workers: 1,
|
workers: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -253,7 +253,8 @@ describe('/asset', () => {
|
|||||||
|
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body.id).toEqual(facesAsset.id);
|
expect(body.id).toEqual(facesAsset.id);
|
||||||
expect(body.people).toMatchObject(expectedFaces);
|
const sortedPeople = body.people.toSorted((a: any, b: any) => a.name.localeCompare(b.name));
|
||||||
|
expect(sortedPeople).toMatchObject(expectedFaces);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ test.describe('Shared Links', () => {
|
|||||||
await page.goto(`/share/${sharedLink.key}`);
|
await page.goto(`/share/${sharedLink.key}`);
|
||||||
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
|
await page.getByRole('heading', { name: 'Test Album' }).waitFor();
|
||||||
await page.locator(`[data-asset-id="${asset.id}"]`).hover();
|
await page.locator(`[data-asset-id="${asset.id}"]`).hover();
|
||||||
await page.waitForSelector('[data-group] svg');
|
await page.waitForSelector(`[data-asset-id="${asset.id}"] [role="checkbox"]`);
|
||||||
await page.getByRole('checkbox').click();
|
|
||||||
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
|
await Promise.all([page.waitForEvent('download'), page.getByRole('button', { name: 'Download' }).click()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ test.describe('Timeline', () => {
|
|||||||
const asset = getAsset(timelineRestData, album.assetIds[0])!;
|
const asset = getAsset(timelineRestData, album.assetIds[0])!;
|
||||||
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
||||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||||
await thumbnailUtils.expectSelectedReadonly(page, asset.id);
|
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
|
||||||
});
|
});
|
||||||
test('Add photos to album', async ({ page }) => {
|
test('Add photos to album', async ({ page }) => {
|
||||||
const album = timelineRestData.album;
|
const album = timelineRestData.album;
|
||||||
@@ -447,7 +447,7 @@ test.describe('Timeline', () => {
|
|||||||
const asset = getAsset(timelineRestData, album.assetIds[0])!;
|
const asset = getAsset(timelineRestData, album.assetIds[0])!;
|
||||||
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
await pageUtils.goToAsset(page, asset.fileCreatedAt);
|
||||||
await thumbnailUtils.expectInViewport(page, asset.id);
|
await thumbnailUtils.expectInViewport(page, asset.id);
|
||||||
await thumbnailUtils.expectSelectedReadonly(page, asset.id);
|
await thumbnailUtils.expectSelectedDisabled(page, asset.id);
|
||||||
await pageUtils.selectDay(page, 'Tue, Feb 27, 2024');
|
await pageUtils.selectDay(page, 'Tue, Feb 27, 2024');
|
||||||
const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => {
|
const put = pageRoutePromise(page, `**/api/albums/${album.id}/assets`, async (route, request) => {
|
||||||
const requestJson = request.postDataJSON();
|
const requestJson = request.postDataJSON();
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const thumbnailUtils = {
|
|||||||
return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"] button`);
|
return page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"] button`);
|
||||||
},
|
},
|
||||||
selectedAsset(page: Page) {
|
selectedAsset(page: Page) {
|
||||||
return page.locator('[data-thumbnail-focus-container]:has(button[aria-checked])');
|
return page.locator('[data-thumbnail-focus-container][data-selected]');
|
||||||
},
|
},
|
||||||
async clickAssetId(page: Page, assetId: string) {
|
async clickAssetId(page: Page, assetId: string) {
|
||||||
await thumbnailUtils.withAssetId(page, assetId).click();
|
await thumbnailUtils.withAssetId(page, assetId).click();
|
||||||
@@ -102,12 +102,9 @@ export const thumbnailUtils = {
|
|||||||
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
|
async expectThumbnailIsNotArchive(page: Page, assetId: string) {
|
||||||
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
|
await expect(thumbnailUtils.withAssetId(page, assetId).locator('[data-icon-archive]')).toHaveCount(0);
|
||||||
},
|
},
|
||||||
async expectSelectedReadonly(page: Page, assetId: string) {
|
async expectSelectedDisabled(page: Page, assetId: string) {
|
||||||
// todo - need a data attribute for selected
|
|
||||||
await expect(
|
await expect(
|
||||||
page.locator(
|
page.locator(`[data-thumbnail-focus-container][data-asset="${assetId}"][data-selected][data-disabled]`),
|
||||||
`[data-thumbnail-focus-container][data-asset="${assetId}"] > .group.cursor-not-allowed > .rounded-xl`,
|
|
||||||
),
|
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
},
|
},
|
||||||
async expectTimelineHasOnScreenAssets(page: Page) {
|
async expectTimelineHasOnScreenAssets(page: Page) {
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
// skip `docker compose up` if `make e2e` was already run
|
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
||||||
|
|
||||||
|
// skip `docker compose up` if `make e2e` was already run or if VITEST_DISABLE_DOCKER_SETUP is set
|
||||||
const globalSetup: string[] = [];
|
const globalSetup: string[] = [];
|
||||||
try {
|
if (!skipDockerSetup) {
|
||||||
await fetch('http://127.0.0.1:2285/api/server/ping');
|
try {
|
||||||
} catch {
|
await fetch('http://127.0.0.1:2285/api/server/ping');
|
||||||
globalSetup.push('src/docker-compose.ts');
|
} catch {
|
||||||
|
globalSetup.push('src/docker-compose.ts');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
|
retry: process.env.CI ? 4 : 0,
|
||||||
include: ['src/specs/server/**/*.e2e-spec.ts'],
|
include: ['src/specs/server/**/*.e2e-spec.ts'],
|
||||||
globalSetup,
|
globalSetup,
|
||||||
testTimeout: 15_000,
|
testTimeout: 15_000,
|
||||||
|
|||||||
28
e2e/vitest.maintenance.config.ts
Normal file
28
e2e/vitest.maintenance.config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
|
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
||||||
|
|
||||||
|
// skip `docker compose up` if `make e2e` was already run or if VITEST_DISABLE_DOCKER_SETUP is set
|
||||||
|
const globalSetup: string[] = [];
|
||||||
|
if (!skipDockerSetup) {
|
||||||
|
try {
|
||||||
|
await fetch('http://127.0.0.1:2285/api/server/ping');
|
||||||
|
} catch {
|
||||||
|
globalSetup.push('src/docker-compose.ts');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
retry: process.env.CI ? 4 : 0,
|
||||||
|
include: ['src/specs/maintenance/server/**/*.e2e-spec.ts'],
|
||||||
|
globalSetup,
|
||||||
|
testTimeout: 15_000,
|
||||||
|
pool: 'threads',
|
||||||
|
poolOptions: {
|
||||||
|
threads: {
|
||||||
|
singleThread: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
16
i18n/en.json
16
i18n/en.json
@@ -1074,6 +1074,7 @@
|
|||||||
"failed_to_update_notification_status": "Failed to update notification status",
|
"failed_to_update_notification_status": "Failed to update notification status",
|
||||||
"incorrect_email_or_password": "Incorrect email or password",
|
"incorrect_email_or_password": "Incorrect email or password",
|
||||||
"library_folder_already_exists": "This import path already exists.",
|
"library_folder_already_exists": "This import path already exists.",
|
||||||
|
"page_not_found": "Page not found :/",
|
||||||
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
|
"paths_validation_failed": "{paths, plural, one {# path} other {# paths}} failed validation",
|
||||||
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
|
"profile_picture_transparent_pixels": "Profile pictures cannot have transparent pixels. Please zoom in and/or move the image.",
|
||||||
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
|
"quota_higher_than_disk_size": "You set a quota higher than the disk size",
|
||||||
@@ -1218,6 +1219,7 @@
|
|||||||
"filter_description": "Conditions to filter the target assets",
|
"filter_description": "Conditions to filter the target assets",
|
||||||
"filter_people": "Filter people",
|
"filter_people": "Filter people",
|
||||||
"filter_places": "Filter places",
|
"filter_places": "Filter places",
|
||||||
|
"filter_tags": "Filter tags",
|
||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
"find_them_fast": "Find them fast by name with search",
|
"find_them_fast": "Find them fast by name with search",
|
||||||
"first": "First",
|
"first": "First",
|
||||||
@@ -1808,9 +1810,8 @@
|
|||||||
"rate_asset": "Rate Asset",
|
"rate_asset": "Rate Asset",
|
||||||
"rating": "Star rating",
|
"rating": "Star rating",
|
||||||
"rating_clear": "Clear rating",
|
"rating_clear": "Clear rating",
|
||||||
"rating_count": "{count, plural, one {# star} other {# stars}}",
|
"rating_count": "{count, plural, =0 {Unrated} one {# star} other {# stars}}",
|
||||||
"rating_description": "Display the EXIF rating in the info panel",
|
"rating_description": "Display the EXIF rating in the info panel",
|
||||||
"rating_set": "Rating set to {rating, plural, one {# star} other {# stars}}",
|
|
||||||
"reaction_options": "Reaction options",
|
"reaction_options": "Reaction options",
|
||||||
"read_changelog": "Read Changelog",
|
"read_changelog": "Read Changelog",
|
||||||
"readonly_mode_disabled": "Read-only mode disabled",
|
"readonly_mode_disabled": "Read-only mode disabled",
|
||||||
@@ -1882,7 +1883,10 @@
|
|||||||
"reset_pin_code_success": "Successfully reset PIN code",
|
"reset_pin_code_success": "Successfully reset PIN code",
|
||||||
"reset_pin_code_with_password": "You can always reset your PIN code with your password",
|
"reset_pin_code_with_password": "You can always reset your PIN code with your password",
|
||||||
"reset_sqlite": "Reset SQLite Database",
|
"reset_sqlite": "Reset SQLite Database",
|
||||||
"reset_sqlite_confirmation": "Are you sure you want to reset the SQLite database? You will need to log out and log in again to resync the data",
|
"reset_sqlite_clear_app_data": "Clear Data",
|
||||||
|
"reset_sqlite_confirmation": "Are you sure you want to clear the app data? This will remove all settings and sign you out.",
|
||||||
|
"reset_sqlite_confirmation_note": "Note: You will need to restart the app after clearing.",
|
||||||
|
"reset_sqlite_done": "App data has been cleared. Please restart Immich and log in again.",
|
||||||
"reset_sqlite_success": "Successfully reset the SQLite database",
|
"reset_sqlite_success": "Successfully reset the SQLite database",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Reset to default",
|
||||||
"resolution": "Resolution",
|
"resolution": "Resolution",
|
||||||
@@ -1910,6 +1914,7 @@
|
|||||||
"saved_settings": "Saved settings",
|
"saved_settings": "Saved settings",
|
||||||
"say_something": "Say something",
|
"say_something": "Say something",
|
||||||
"scaffold_body_error_occurred": "Error occurred",
|
"scaffold_body_error_occurred": "Error occurred",
|
||||||
|
"scaffold_body_error_unrecoverable": "An unrecoverable error has occurred. Please share the error and stack trace on Discord or GitHub so we can help. If advised, you can clear the app data below.",
|
||||||
"scan": "Scan",
|
"scan": "Scan",
|
||||||
"scan_all_libraries": "Scan All Libraries",
|
"scan_all_libraries": "Scan All Libraries",
|
||||||
"scan_library": "Scan",
|
"scan_library": "Scan",
|
||||||
@@ -1945,6 +1950,7 @@
|
|||||||
"search_filter_ocr": "Search by OCR",
|
"search_filter_ocr": "Search by OCR",
|
||||||
"search_filter_people_title": "Select people",
|
"search_filter_people_title": "Select people",
|
||||||
"search_filter_star_rating": "Star Rating",
|
"search_filter_star_rating": "Star Rating",
|
||||||
|
"search_filter_tags_title": "Select tags",
|
||||||
"search_for": "Search for",
|
"search_for": "Search for",
|
||||||
"search_for_existing_person": "Search for existing person",
|
"search_for_existing_person": "Search for existing person",
|
||||||
"search_no_more_result": "No more results",
|
"search_no_more_result": "No more results",
|
||||||
@@ -2024,6 +2030,9 @@
|
|||||||
"set_profile_picture": "Set profile picture",
|
"set_profile_picture": "Set profile picture",
|
||||||
"set_slideshow_to_fullscreen": "Set Slideshow to fullscreen",
|
"set_slideshow_to_fullscreen": "Set Slideshow to fullscreen",
|
||||||
"set_stack_primary_asset": "Set as primary asset",
|
"set_stack_primary_asset": "Set as primary asset",
|
||||||
|
"setting_image_navigation_enable_subtitle": "If enabled, you can navigate to the previous/next image by tapping the leftmost/rightmost quarter of the screen.",
|
||||||
|
"setting_image_navigation_enable_title": "Tap to Navigate",
|
||||||
|
"setting_image_navigation_title": "Image Navigation",
|
||||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||||
"setting_image_viewer_original_title": "Load original image",
|
"setting_image_viewer_original_title": "Load original image",
|
||||||
@@ -2302,6 +2311,7 @@
|
|||||||
"unstack_action_prompt": "{count} unstacked",
|
"unstack_action_prompt": "{count} unstacked",
|
||||||
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
|
"unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}",
|
||||||
"unsupported_field_type": "Unsupported field type",
|
"unsupported_field_type": "Unsupported field type",
|
||||||
|
"unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.",
|
||||||
"untagged": "Untagged",
|
"untagged": "Untagged",
|
||||||
"untitled_workflow": "Untitled workflow",
|
"untitled_workflow": "Untitled workflow",
|
||||||
"up_next": "Up next",
|
"up_next": "Up next",
|
||||||
|
|||||||
1
machine-learning/.python-version
Normal file
1
machine-learning/.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
@@ -22,48 +22,7 @@ FROM builder-cpu AS builder-rknn
|
|||||||
|
|
||||||
# Warning: 25GiB+ disk space required to pull this image
|
# Warning: 25GiB+ disk space required to pull this image
|
||||||
# TODO: find a way to reduce the image size
|
# TODO: find a way to reduce the image size
|
||||||
FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS builder-rocm
|
FROM rocm/dev-ubuntu-24.04:7.2-complete@sha256:86e11093b4a7ec2a79b1b6701d10e840a6994f21c7e05929b51eb9be361c683a AS builder-rocm
|
||||||
|
|
||||||
# renovate: datasource=github-releases depName=Microsoft/onnxruntime
|
|
||||||
ARG ONNXRUNTIME_VERSION="v1.22.1"
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends wget git
|
|
||||||
RUN wget -nv https://github.com/Kitware/CMake/releases/download/v3.31.9/cmake-3.31.9-linux-x86_64.sh && \
|
|
||||||
chmod +x cmake-3.31.9-linux-x86_64.sh && \
|
|
||||||
mkdir -p /code/cmake-3.31.9-linux-x86_64 && \
|
|
||||||
./cmake-3.31.9-linux-x86_64.sh --skip-license --prefix=/code/cmake-3.31.9-linux-x86_64 && \
|
|
||||||
rm cmake-3.31.9-linux-x86_64.sh
|
|
||||||
|
|
||||||
RUN git clone --single-branch --branch "${ONNXRUNTIME_VERSION}" --recursive "https://github.com/Microsoft/onnxruntime" onnxruntime
|
|
||||||
WORKDIR /code/onnxruntime
|
|
||||||
# Fix for multi-threading based on comments in https://github.com/microsoft/onnxruntime/pull/19567
|
|
||||||
# TODO: find a way to fix this without disabling algo caching
|
|
||||||
COPY ./patches/* /tmp/
|
|
||||||
RUN git apply /tmp/*.patch
|
|
||||||
|
|
||||||
RUN /bin/sh ./dockerfiles/scripts/install_common_deps.sh
|
|
||||||
|
|
||||||
ENV PATH=/opt/rocm-venv/bin:/code/cmake-3.31.9-linux-x86_64/bin:${PATH}
|
|
||||||
ENV CCACHE_DIR="/ccache"
|
|
||||||
# Note: the `parallel` setting uses a substantial amount of RAM
|
|
||||||
RUN --mount=type=cache,target=/ccache \
|
|
||||||
./build.sh \
|
|
||||||
--allow_running_as_root \
|
|
||||||
--config Release \
|
|
||||||
--build_wheel \
|
|
||||||
--update \
|
|
||||||
--build \
|
|
||||||
--parallel 48 \
|
|
||||||
--cmake_extra_defines \
|
|
||||||
ONNXRUNTIME_VERSION="${ONNXRUNTIME_VERSION}" \
|
|
||||||
CMAKE_HIP_ARCHITECTURES="gfx900;gfx906;gfx908;gfx90a;gfx940;gfx941;gfx942;gfx1030;gfx1100;gfx1101;gfx1102;gfx1200;gfx1201" \
|
|
||||||
--skip_tests \
|
|
||||||
--use_rocm \
|
|
||||||
--rocm_home=/opt/rocm \
|
|
||||||
--use_cache \
|
|
||||||
--compile_no_warning_as_error
|
|
||||||
RUN mv /code/onnxruntime/build/Linux/Release/dist/*.whl /opt/
|
|
||||||
|
|
||||||
FROM builder-${DEVICE} AS builder
|
FROM builder-${DEVICE} AS builder
|
||||||
|
|
||||||
@@ -79,9 +38,6 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
|||||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
|
uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy
|
||||||
RUN if [ "$DEVICE" = "rocm" ]; then \
|
|
||||||
uv pip install /opt/onnxruntime_rocm-*.whl; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:04cd27899595a99dfe77709d96f08876bf2ee99139ee2f0fe9ac948005034e5b AS prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:04cd27899595a99dfe77709d96f08876bf2ee99139ee2f0fe9ac948005034e5b AS prod-cpu
|
||||||
|
|
||||||
@@ -92,14 +48,14 @@ FROM python:3.13-slim-trixie@sha256:3de9a8d7aedbb7984dc18f2dff178a7850f16c1ae7c3
|
|||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-core-2_2.27.10+20617_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-opencl-2_2.27.10+20617_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/intel-opencl-icd_26.01.36711.4-0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
||||||
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/libigdgmm12_22.9.0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \
|
||||||
dpkg -i *.deb && \
|
dpkg -i *.deb && \
|
||||||
rm *.deb && \
|
rm *.deb && \
|
||||||
apt-get remove wget -yqq && \
|
apt-get remove wget -yqq && \
|
||||||
@@ -120,7 +76,11 @@ COPY --from=builder-cuda /usr/local/bin/python3 /usr/local/bin/python3
|
|||||||
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
COPY --from=builder-cuda /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
||||||
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
|
COPY --from=builder-cuda /usr/local/lib/libpython3.11.so /usr/local/lib/libpython3.11.so
|
||||||
|
|
||||||
FROM rocm/dev-ubuntu-24.04:6.4.4-complete@sha256:31418ac10a3769a71eaef330c07280d1d999d7074621339b8f93c484c35f6078 AS prod-rocm
|
FROM rocm/dev-ubuntu-24.04:7.2-complete@sha256:86e11093b4a7ec2a79b1b6701d10e840a6994f21c7e05929b51eb9be361c683a AS prod-rocm
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -yqq migraphx miopen-hip && \
|
||||||
|
apt-get clean && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
FROM prod-cpu AS prod-armnn
|
FROM prod-cpu AS prod-armnn
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class Settings(BaseSettings):
|
|||||||
preload: PreloadModelData | None = None
|
preload: PreloadModelData | None = None
|
||||||
max_batch_size: MaxBatchSize | None = None
|
max_batch_size: MaxBatchSize | None = None
|
||||||
openvino_precision: ModelPrecision = ModelPrecision.FP32
|
openvino_precision: ModelPrecision = ModelPrecision.FP32
|
||||||
|
rocm_precision: ModelPrecision = ModelPrecision.FP32
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_id(self) -> str:
|
def device_id(self) -> str:
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ _PADDLE_MODELS = {
|
|||||||
|
|
||||||
SUPPORTED_PROVIDERS = [
|
SUPPORTED_PROVIDERS = [
|
||||||
"CUDAExecutionProvider",
|
"CUDAExecutionProvider",
|
||||||
"ROCMExecutionProvider",
|
"MIGraphXExecutionProvider",
|
||||||
"OpenVINOExecutionProvider",
|
"OpenVINOExecutionProvider",
|
||||||
"CoreMLExecutionProvider",
|
"CoreMLExecutionProvider",
|
||||||
"CPUExecutionProvider",
|
"CPUExecutionProvider",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import onnxruntime as ort
|
|||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
from immich_ml.models.constants import SUPPORTED_PROVIDERS
|
from immich_ml.models.constants import SUPPORTED_PROVIDERS
|
||||||
from immich_ml.schemas import SessionNode
|
from immich_ml.schemas import ModelPrecision, SessionNode
|
||||||
|
|
||||||
from ..config import log, settings
|
from ..config import log, settings
|
||||||
|
|
||||||
@@ -90,8 +90,17 @@ class OrtSession:
|
|||||||
match provider:
|
match provider:
|
||||||
case "CPUExecutionProvider":
|
case "CPUExecutionProvider":
|
||||||
options = {"arena_extend_strategy": "kSameAsRequested"}
|
options = {"arena_extend_strategy": "kSameAsRequested"}
|
||||||
case "CUDAExecutionProvider" | "ROCMExecutionProvider":
|
case "CUDAExecutionProvider":
|
||||||
options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id}
|
options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id}
|
||||||
|
case "MIGraphXExecutionProvider":
|
||||||
|
migraphx_dir = self.model_path.parent / "migraphx"
|
||||||
|
# MIGraphX does not create the underlying folder and will crash if it does not exist
|
||||||
|
migraphx_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
options = {
|
||||||
|
"device_id": settings.device_id,
|
||||||
|
"migraphx_model_cache_dir": migraphx_dir.as_posix(),
|
||||||
|
"migraphx_fp16_enable": "1" if settings.rocm_precision == ModelPrecision.FP16 else "0",
|
||||||
|
}
|
||||||
case "OpenVINOExecutionProvider":
|
case "OpenVINOExecutionProvider":
|
||||||
openvino_dir = self.model_path.parent / "openvino"
|
openvino_dir = self.model_path.parent / "openvino"
|
||||||
device = f"GPU.{settings.device_id}"
|
device = f"GPU.{settings.device_id}"
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
commit 16839b58d9b3c3162a67ce5d776b36d4d24e801f
|
|
||||||
Author: mertalev <101130780+mertalev@users.noreply.github.com>
|
|
||||||
Date: Wed Mar 5 11:25:38 2025 -0500
|
|
||||||
|
|
||||||
disable algo caching (attributed to @dmnieto in https://github.com/microsoft/onnxruntime/pull/19567)
|
|
||||||
|
|
||||||
diff --git a/onnxruntime/core/providers/rocm/nn/conv.cc b/onnxruntime/core/providers/rocm/nn/conv.cc
|
|
||||||
index d7f47d07a8..4060a2af52 100644
|
|
||||||
--- a/onnxruntime/core/providers/rocm/nn/conv.cc
|
|
||||||
+++ b/onnxruntime/core/providers/rocm/nn/conv.cc
|
|
||||||
@@ -127,7 +127,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
|
|
||||||
|
|
||||||
if (w_dims_changed) {
|
|
||||||
s_.last_w_dims = gsl::make_span(w_dims);
|
|
||||||
- s_.cached_benchmark_fwd_results.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
ORT_RETURN_IF_ERROR(conv_attrs_.ValidateInputShape(X->Shape(), W->Shape(), channels_last, channels_last));
|
|
||||||
@@ -277,35 +276,6 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
|
|
||||||
HIP_CALL_THROW(hipMalloc(&s_.b_zero, malloc_size));
|
|
||||||
HIP_CALL_THROW(hipMemsetAsync(s_.b_zero, 0, malloc_size, Stream(context)));
|
|
||||||
}
|
|
||||||
-
|
|
||||||
- if (!s_.cached_benchmark_fwd_results.contains(x_dims_miopen)) {
|
|
||||||
- miopenConvAlgoPerf_t perf;
|
|
||||||
- int algo_count = 1;
|
|
||||||
- const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
|
|
||||||
- static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
|
|
||||||
- size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId())
|
|
||||||
- : AlgoSearchWorkspaceSize;
|
|
||||||
- IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
|
|
||||||
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
|
|
||||||
- GetMiopenHandle(context),
|
|
||||||
- s_.x_tensor,
|
|
||||||
- s_.x_data,
|
|
||||||
- s_.w_desc,
|
|
||||||
- s_.w_data,
|
|
||||||
- s_.conv_desc,
|
|
||||||
- s_.y_tensor,
|
|
||||||
- s_.y_data,
|
|
||||||
- 1, // requestedAlgoCount
|
|
||||||
- &algo_count, // returnedAlgoCount
|
|
||||||
- &perf,
|
|
||||||
- algo_search_workspace.get(),
|
|
||||||
- max_ws_size,
|
|
||||||
- false)); // Do not do exhaustive algo search.
|
|
||||||
- s_.cached_benchmark_fwd_results.insert(x_dims_miopen, {perf.fwd_algo, perf.memory});
|
|
||||||
- }
|
|
||||||
- const auto& perf = s_.cached_benchmark_fwd_results.at(x_dims_miopen);
|
|
||||||
- s_.fwd_algo = perf.fwd_algo;
|
|
||||||
- s_.workspace_bytes = perf.memory;
|
|
||||||
} else {
|
|
||||||
// set Y
|
|
||||||
s_.Y = context->Output(0, TensorShape(s_.y_dims));
|
|
||||||
@@ -319,6 +289,31 @@ Status Conv<T, NHWC>::UpdateState(OpKernelContext* context, bool bias_expected)
|
|
||||||
s_.y_data = reinterpret_cast<HipT*>(s_.Y->MutableData<T>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ miopenConvAlgoPerf_t perf;
|
|
||||||
+ int algo_count = 1;
|
|
||||||
+ const ROCMExecutionProvider* rocm_ep = static_cast<const ROCMExecutionProvider*>(this->Info().GetExecutionProvider());
|
|
||||||
+ static constexpr int num_algos = MIOPEN_CONVOLUTION_FWD_ALGO_COUNT;
|
|
||||||
+ size_t max_ws_size = rocm_ep->GetMiopenConvUseMaxWorkspace() ? GetMaxWorkspaceSize(GetMiopenHandle(context), s_, kAllAlgos, num_algos, rocm_ep->GetDeviceId())
|
|
||||||
+ : AlgoSearchWorkspaceSize;
|
|
||||||
+ IAllocatorUniquePtr<void> algo_search_workspace = GetTransientScratchBuffer<void>(max_ws_size);
|
|
||||||
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionForwardAlgorithm(
|
|
||||||
+ GetMiopenHandle(context),
|
|
||||||
+ s_.x_tensor,
|
|
||||||
+ s_.x_data,
|
|
||||||
+ s_.w_desc,
|
|
||||||
+ s_.w_data,
|
|
||||||
+ s_.conv_desc,
|
|
||||||
+ s_.y_tensor,
|
|
||||||
+ s_.y_data,
|
|
||||||
+ 1, // requestedAlgoCount
|
|
||||||
+ &algo_count, // returnedAlgoCount
|
|
||||||
+ &perf,
|
|
||||||
+ algo_search_workspace.get(),
|
|
||||||
+ max_ws_size,
|
|
||||||
+ false)); // Do not do exhaustive algo search.
|
|
||||||
+ s_.fwd_algo = perf.fwd_algo;
|
|
||||||
+ s_.workspace_bytes = perf.memory;
|
|
||||||
return Status::OK();
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/onnxruntime/core/providers/rocm/nn/conv.h b/onnxruntime/core/providers/rocm/nn/conv.h
|
|
||||||
index bc9846203e..d54218f258 100644
|
|
||||||
--- a/onnxruntime/core/providers/rocm/nn/conv.h
|
|
||||||
+++ b/onnxruntime/core/providers/rocm/nn/conv.h
|
|
||||||
@@ -108,9 +108,6 @@ class lru_unordered_map {
|
|
||||||
list_type lru_list_;
|
|
||||||
};
|
|
||||||
|
|
||||||
-// cached miopen descriptors
|
|
||||||
-constexpr size_t MAX_CACHED_ALGO_PERF_RESULTS = 10000;
|
|
||||||
-
|
|
||||||
template <typename AlgoPerfType>
|
|
||||||
struct MiopenConvState {
|
|
||||||
// if x/w dims changed, update algo and miopenTensors
|
|
||||||
@@ -148,9 +145,6 @@ struct MiopenConvState {
|
|
||||||
decltype(AlgoPerfType().memory) memory;
|
|
||||||
};
|
|
||||||
|
|
||||||
- lru_unordered_map<TensorShapeVector, PerfFwdResultParams, vector_hash> cached_benchmark_fwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
|
|
||||||
- lru_unordered_map<TensorShapeVector, PerfBwdResultParams, vector_hash> cached_benchmark_bwd_results{MAX_CACHED_ALGO_PERF_RESULTS};
|
|
||||||
-
|
|
||||||
// Some properties needed to support asymmetric padded Conv nodes
|
|
||||||
bool post_slicing_required;
|
|
||||||
TensorShapeVector slice_starts;
|
|
||||||
diff --git a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
|
|
||||||
index 7447113fdf..a662e35b2e 100644
|
|
||||||
--- a/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
|
|
||||||
+++ b/onnxruntime/core/providers/rocm/nn/conv_transpose.cc
|
|
||||||
@@ -76,7 +76,6 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
|
|
||||||
|
|
||||||
if (w_dims_changed) {
|
|
||||||
s_.last_w_dims = gsl::make_span(w_dims);
|
|
||||||
- s_.cached_benchmark_bwd_results.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
ConvTransposeAttributes::Prepare p;
|
|
||||||
@@ -126,35 +125,29 @@ Status ConvTranspose<T, NHWC>::DoConvTranspose(OpKernelContext* context, bool dy
|
|
||||||
}
|
|
||||||
|
|
||||||
y_data = reinterpret_cast<HipT*>(p.Y->MutableData<T>());
|
|
||||||
-
|
|
||||||
- if (!s_.cached_benchmark_bwd_results.contains(x_dims)) {
|
|
||||||
- IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
|
|
||||||
-
|
|
||||||
- miopenConvAlgoPerf_t perf;
|
|
||||||
- int algo_count = 1;
|
|
||||||
- MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
|
|
||||||
- GetMiopenHandle(context),
|
|
||||||
- s_.x_tensor,
|
|
||||||
- x_data,
|
|
||||||
- s_.w_desc,
|
|
||||||
- w_data,
|
|
||||||
- s_.conv_desc,
|
|
||||||
- s_.y_tensor,
|
|
||||||
- y_data,
|
|
||||||
- 1,
|
|
||||||
- &algo_count,
|
|
||||||
- &perf,
|
|
||||||
- algo_search_workspace.get(),
|
|
||||||
- AlgoSearchWorkspaceSize,
|
|
||||||
- false));
|
|
||||||
- s_.cached_benchmark_bwd_results.insert(x_dims, {perf.bwd_data_algo, perf.memory});
|
|
||||||
- }
|
|
||||||
-
|
|
||||||
- const auto& perf = s_.cached_benchmark_bwd_results.at(x_dims);
|
|
||||||
- s_.bwd_data_algo = perf.bwd_data_algo;
|
|
||||||
- s_.workspace_bytes = perf.memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ IAllocatorUniquePtr<void> algo_search_workspace = GetScratchBuffer<void>(AlgoSearchWorkspaceSize, context->GetComputeStream());
|
|
||||||
+ miopenConvAlgoPerf_t perf;
|
|
||||||
+ int algo_count = 1;
|
|
||||||
+ MIOPEN_RETURN_IF_ERROR(miopenFindConvolutionBackwardDataAlgorithm(
|
|
||||||
+ GetMiopenHandle(context),
|
|
||||||
+ s_.x_tensor,
|
|
||||||
+ x_data,
|
|
||||||
+ s_.w_desc,
|
|
||||||
+ w_data,
|
|
||||||
+ s_.conv_desc,
|
|
||||||
+ s_.y_tensor,
|
|
||||||
+ y_data,
|
|
||||||
+ 1,
|
|
||||||
+ &algo_count,
|
|
||||||
+ &perf,
|
|
||||||
+ algo_search_workspace.get(),
|
|
||||||
+ AlgoSearchWorkspaceSize,
|
|
||||||
+ false));
|
|
||||||
+ s_.bwd_data_algo = perf.bwd_data_algo;
|
|
||||||
+ s_.workspace_bytes = perf.memory;
|
|
||||||
+
|
|
||||||
// The following block will be executed in case there has been no change in the shapes of the
|
|
||||||
// input and the filter compared to the previous run
|
|
||||||
if (!y_data) {
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
diff --git a/dockerfiles/scripts/install_common_deps.sh b/dockerfiles/scripts/install_common_deps.sh
|
|
||||||
index bbb672a99e..0dc652fbda 100644
|
|
||||||
--- a/dockerfiles/scripts/install_common_deps.sh
|
|
||||||
+++ b/dockerfiles/scripts/install_common_deps.sh
|
|
||||||
@@ -8,16 +8,23 @@ apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
curl \
|
|
||||||
libcurl4-openssl-dev \
|
|
||||||
libssl-dev \
|
|
||||||
- python3-dev
|
|
||||||
+ python3-dev \
|
|
||||||
+ ccache
|
|
||||||
|
|
||||||
# Dependencies: conda
|
|
||||||
-wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh -O ~/miniconda.sh --no-check-certificate && /bin/bash ~/miniconda.sh -b -p /opt/miniconda
|
|
||||||
+wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-py312_25.9.1-1-Linux-x86_64.sh -O ~/miniconda.sh && /bin/bash ~/miniconda.sh -b -p /opt/miniconda
|
|
||||||
rm ~/miniconda.sh
|
|
||||||
/opt/miniconda/bin/conda clean -ya
|
|
||||||
|
|
||||||
-pip install numpy
|
|
||||||
-pip install packaging
|
|
||||||
-pip install "wheel>=0.35.1"
|
|
||||||
+# Dependencies: venv and packages
|
|
||||||
+/opt/miniconda/bin/python3 -m venv /opt/rocm-venv
|
|
||||||
+/opt/rocm-venv/bin/pip install --no-cache-dir --upgrade pip
|
|
||||||
+/opt/rocm-venv/bin/pip install --no-cache-dir \
|
|
||||||
+ "numpy==2.3.4" \
|
|
||||||
+ "packaging==25.0" \
|
|
||||||
+ "wheel==0.45.1" \
|
|
||||||
+ "setuptools==80.9.0"
|
|
||||||
+
|
|
||||||
rm -rf /opt/miniconda/pkgs
|
|
||||||
|
|
||||||
# Dependencies: cmake
|
|
||||||
@@ -8,7 +8,6 @@ readme = "README.md"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aiocache>=0.12.1,<1.0",
|
"aiocache>=0.12.1,<1.0",
|
||||||
"fastapi>=0.95.2,<1.0",
|
"fastapi>=0.95.2,<1.0",
|
||||||
"ftfy>=6.1.1",
|
|
||||||
"gunicorn>=21.1.0",
|
"gunicorn>=21.1.0",
|
||||||
"huggingface-hub>=0.20.1,<1.0",
|
"huggingface-hub>=0.20.1,<1.0",
|
||||||
"insightface>=0.7.3,<1.0",
|
"insightface>=0.7.3,<1.0",
|
||||||
@@ -50,10 +49,10 @@ dev = ["locust>=2.15.1", { include-group = "test" }, { include-group = "lint" }]
|
|||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
cpu = ["onnxruntime>=1.23.2,<2"]
|
cpu = ["onnxruntime>=1.23.2,<2"]
|
||||||
cuda = ["onnxruntime-gpu>=1.23.2,<2"]
|
cuda = ["onnxruntime-gpu>=1.23.2,<2"]
|
||||||
openvino = ["onnxruntime-openvino>=1.23.0,<2"]
|
openvino = ["onnxruntime-openvino>=1.24.1,<2"]
|
||||||
armnn = ["onnxruntime>=1.23.2,<2"]
|
armnn = ["onnxruntime>=1.23.2,<2"]
|
||||||
rknn = ["onnxruntime>=1.23.2,<2", "rknn-toolkit-lite2>=2.3.0,<3"]
|
rknn = ["onnxruntime>=1.23.2,<2", "rknn-toolkit-lite2>=2.3.0,<3"]
|
||||||
rocm = []
|
rocm = ["onnxruntime-migraphx>=1.23.2,<2"]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
compile-bytecode = true
|
compile-bytecode = true
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class TestOrtSession:
|
|||||||
OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"]
|
||||||
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
|
CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"]
|
||||||
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
|
TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"]
|
||||||
ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"]
|
ROCM_EP = ["MIGraphXExecutionProvider", "CPUExecutionProvider"]
|
||||||
COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"]
|
COREML_EP = ["CoreMLExecutionProvider", "CPUExecutionProvider"]
|
||||||
|
|
||||||
@pytest.mark.providers(CPU_EP)
|
@pytest.mark.providers(CPU_EP)
|
||||||
@@ -289,12 +289,38 @@ class TestOrtSession:
|
|||||||
|
|
||||||
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
|
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
|
||||||
|
|
||||||
def test_sets_provider_options_for_rocm(self) -> None:
|
def test_sets_provider_options_for_rocm(self, mocker: MockerFixture) -> None:
|
||||||
|
model_path = "/cache/ViT-B-32__openai/textual/model.onnx"
|
||||||
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
|
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
|
||||||
|
mkdir = mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
||||||
|
|
||||||
session = OrtSession("ViT-B-32__openai", providers=["ROCMExecutionProvider"])
|
session = OrtSession(model_path, providers=["MIGraphXExecutionProvider"])
|
||||||
|
|
||||||
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
|
assert session.provider_options == [
|
||||||
|
{
|
||||||
|
"device_id": "1",
|
||||||
|
"migraphx_model_cache_dir": "/cache/ViT-B-32__openai/textual/migraphx",
|
||||||
|
"migraphx_fp16_enable": "0",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def test_sets_rocm_to_fp16_if_enabled(self, path: mock.Mock, mocker: MockerFixture) -> None:
|
||||||
|
model_path = "/cache/ViT-B-32__openai/textual/model.onnx"
|
||||||
|
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
|
||||||
|
mocker.patch.object(settings, "rocm_precision", ModelPrecision.FP16)
|
||||||
|
mkdir = mocker.patch("immich_ml.sessions.ort.Path.mkdir")
|
||||||
|
|
||||||
|
session = OrtSession(model_path, providers=["MIGraphXExecutionProvider"])
|
||||||
|
|
||||||
|
assert session.provider_options == [
|
||||||
|
{
|
||||||
|
"device_id": "1",
|
||||||
|
"migraphx_model_cache_dir": "/cache/ViT-B-32__openai/textual/migraphx",
|
||||||
|
"migraphx_fp16_enable": "1",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def test_sets_provider_options_kwarg(self) -> None:
|
def test_sets_provider_options_kwarg(self) -> None:
|
||||||
session = OrtSession(
|
session = OrtSession(
|
||||||
|
|||||||
165
machine-learning/uv.lock
generated
165
machine-learning/uv.lock
generated
@@ -262,18 +262,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "coloredlogs"
|
|
||||||
version = "15.0.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "humanfriendly" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorlog"
|
name = "colorlog"
|
||||||
version = "6.9.0"
|
version = "6.9.0"
|
||||||
@@ -654,18 +642,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" },
|
{ url = "https://files.pythonhosted.org/packages/70/25/fab23259a52ece5670dcb8452e1af34b89e6135ecc17cd4b54b4b479eac6/fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960", size = 168979, upload-time = "2023-12-11T21:19:52.446Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ftfy"
|
|
||||||
version = "6.3.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "wcwidth" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gevent"
|
name = "gevent"
|
||||||
version = "24.10.3"
|
version = "24.10.3"
|
||||||
@@ -788,14 +764,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gunicorn"
|
name = "gunicorn"
|
||||||
version = "23.0.0"
|
version = "25.1.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/66/13/ef67f59f6a7896fdc2c1d62b5665c5219d6b0a9a1784938eb9a28e55e128/gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616", size = 594377, upload-time = "2026-02-13T11:09:58.989Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/73/4ad5b1f6a2e21cf1e85afdaad2b7b1a933985e2f5d679147a1953aaa192c/gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b", size = 197067, upload-time = "2026-02-13T11:09:57.146Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -898,18 +874,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "humanfriendly"
|
|
||||||
version = "10.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.11"
|
version = "3.11"
|
||||||
@@ -939,7 +903,6 @@ source = { editable = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiocache" },
|
{ name = "aiocache" },
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
{ name = "ftfy" },
|
|
||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
{ name = "huggingface-hub" },
|
{ name = "huggingface-hub" },
|
||||||
{ name = "insightface" },
|
{ name = "insightface" },
|
||||||
@@ -973,6 +936,9 @@ rknn = [
|
|||||||
{ name = "onnxruntime" },
|
{ name = "onnxruntime" },
|
||||||
{ name = "rknn-toolkit-lite2" },
|
{ name = "rknn-toolkit-lite2" },
|
||||||
]
|
]
|
||||||
|
rocm = [
|
||||||
|
{ name = "onnxruntime-migraphx" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
@@ -1018,7 +984,6 @@ types = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "aiocache", specifier = ">=0.12.1,<1.0" },
|
{ name = "aiocache", specifier = ">=0.12.1,<1.0" },
|
||||||
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
{ name = "fastapi", specifier = ">=0.95.2,<1.0" },
|
||||||
{ name = "ftfy", specifier = ">=6.1.1" },
|
|
||||||
{ name = "gunicorn", specifier = ">=21.1.0" },
|
{ name = "gunicorn", specifier = ">=21.1.0" },
|
||||||
{ name = "huggingface-hub", specifier = ">=0.20.1,<1.0" },
|
{ name = "huggingface-hub", specifier = ">=0.20.1,<1.0" },
|
||||||
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
{ name = "insightface", specifier = ">=0.7.3,<1.0" },
|
||||||
@@ -1027,7 +992,8 @@ requires-dist = [
|
|||||||
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.23.2,<2" },
|
{ name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.23.2,<2" },
|
||||||
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.23.0,<2" },
|
{ name = "onnxruntime-migraphx", marker = "extra == 'rocm'", specifier = ">=1.23.2,<2" },
|
||||||
|
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" },
|
||||||
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
|
||||||
{ name = "orjson", specifier = ">=3.9.5" },
|
{ name = "orjson", specifier = ">=3.9.5" },
|
||||||
{ name = "pillow", specifier = ">=12.1.1,<12.2" },
|
{ name = "pillow", specifier = ">=12.1.1,<12.2" },
|
||||||
@@ -1443,32 +1409,55 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "msgpack"
|
name = "msgpack"
|
||||||
version = "1.0.7"
|
version = "1.1.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/d5/5662032db1571110b5b51647aed4b56dfbd01bfae789fa566a2be1f385d1/msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87", size = 166311, upload-time = "2023-09-28T13:20:36.726Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/b3/309de40dc7406b7f3492332c5ee2b492a593c2a9bb97ea48ebf2f5279999/msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84", size = 305096, upload-time = "2023-09-28T13:18:49.678Z" },
|
{ url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/15/56/a677cd761a2cefb2e3ffe7e684633294dccb161d78e8ea6da9277e45b4a2/msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93", size = 235210, upload-time = "2023-09-28T13:18:51.039Z" },
|
{ url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/4e/1ab4a982cbd90f988e49f849fc1212f2c04a59870c59daabf8950617e2aa/msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8", size = 231952, upload-time = "2023-09-28T13:18:52.871Z" },
|
{ url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/74/bd02044eb628c7361ad2bd8c1a6147af5c6c2bbceb77b3b1da20f4a8a9c5/msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46", size = 549511, upload-time = "2023-09-28T13:18:54.422Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/df/09/dee50913ba5cc047f7fd7162f09453a676e7935c84b3bf3a398e12108677/msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b", size = 557980, upload-time = "2023-09-28T13:18:56.058Z" },
|
{ url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/a5/78a7d87f5f8ffe4c32167afa15d4957db649bab4822f909d8d765339bbab/msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e", size = 545547, upload-time = "2023-09-28T13:18:57.396Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/53/698c10913947f97f6fe7faad86a34e6aa1b66cea2df6f99105856bd346d9/msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002", size = 554669, upload-time = "2023-09-28T13:18:58.957Z" },
|
{ url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/3f/9730c6cb574b15d349b80cd8523a7df4b82058528339f952ea1c32ac8a10/msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c", size = 583353, upload-time = "2023-09-28T13:19:01.186Z" },
|
{ url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/bc/dc184d943692671149848438fb3bed3a3de288ce7998cb91bc98f40f201b/msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e", size = 557455, upload-time = "2023-09-28T13:19:03.201Z" },
|
{ url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cf/7b/1bc69d4a56c8d2f4f2dfbe4722d40344af9a85b6fb3b09cfb350ba6a42f6/msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1", size = 216367, upload-time = "2023-09-28T13:19:04.554Z" },
|
{ url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/3d/c8dd23050eefa3d9b9c5b8329ed3308c2f2f80f65825e9ea4b7fa621cdab/msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82", size = 222860, upload-time = "2023-09-28T13:19:06.397Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/47/20dff6b4512cf3575550c8801bc53fe7d540f4efef9c5c37af51760fcdcf/msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b", size = 305759, upload-time = "2023-09-28T13:19:08.148Z" },
|
{ url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6f/8a/34f1726d2c9feccec3d946776e9bce8f20ae09d8b91899fc20b296c942af/msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4", size = 235330, upload-time = "2023-09-28T13:19:09.417Z" },
|
{ url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/f6/e64c72577d6953789c3cb051b059a4b56317056b3c65013952338ed8a34e/msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee", size = 232537, upload-time = "2023-09-28T13:19:10.898Z" },
|
{ url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/75/1ed3a96e12941873fd957e016cc40c0c178861a872bd45e75b9a188eb422/msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5", size = 546561, upload-time = "2023-09-28T13:19:12.779Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/0a/c6a1390f9c6a31da0fecbbfdb86b1cb39ad302d9e24f9cca3d9e14c364f0/msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672", size = 559009, upload-time = "2023-09-28T13:19:14.373Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/74/99f6077754665613ea1f37b3d91c10129f6976b7721ab4d0973023808e5a/msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075", size = 543882, upload-time = "2023-09-28T13:19:16.277Z" },
|
{ url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9c/7e/dc0dc8de2bf27743b31691149258f9b1bd4bf3c44c105df3df9b97081cd1/msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba", size = 546949, upload-time = "2023-09-28T13:19:18.114Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/78/61/91bae9474def032f6c333d62889bbeda9e1554c6b123375ceeb1767efd78/msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c", size = 579836, upload-time = "2023-09-28T13:19:19.729Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/4d/d98592099d4f18945f89cf3e634dc0cb128bb33b1b93f85a84173d35e181/msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5", size = 556587, upload-time = "2023-09-28T13:19:21.666Z" },
|
{ url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/44/6556ffe169bf2c0e974e2ea25fb82a7e55ebcf52a81b03a5e01820de5f84/msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9", size = 216509, upload-time = "2023-09-28T13:19:23.161Z" },
|
{ url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/c1/63903f30d51d165e132e5221a2a4a1bbfab7508b68131c871d70bffac78a/msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf", size = 223287, upload-time = "2023-09-28T13:19:25.097Z" },
|
{ url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1716,11 +1705,10 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "onnxruntime-openvino"
|
name = "onnxruntime-migraphx"
|
||||||
version = "1.23.0"
|
version = "1.24.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "coloredlogs" },
|
|
||||||
{ name = "flatbuffers" },
|
{ name = "flatbuffers" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
@@ -1728,12 +1716,30 @@ dependencies = [
|
|||||||
{ name = "sympy" },
|
{ name = "sympy" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/5a/10/adcd4ac68ffc8dee003553125ef5c091be822e2d7c1077d0bb85690baa9c/onnxruntime_openvino-1.23.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:91938837e6e92e30c63d12fad68a8a4959c40d2eade2bd60f38bdd5b6392f8d3", size = 70481480, upload-time = "2025-10-14T15:19:45.882Z" },
|
{ url = "https://files.pythonhosted.org/packages/dd/da/ca7ebc1a8d1193c97ceb9a05fad50f675eb955dc51beb7eb9ba89c8e7db0/onnxruntime_migraphx-1.24.2-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:a2b434fb8880cac2b268950bdf279f33741d29c1f1c5461d27af835e8e288043", size = 20339710, upload-time = "2026-02-21T07:25:13.17Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/95/25f28d6fecf300aa0af393e96af9e00cc676e5dab650ab84f2122610df50/onnxruntime_openvino-1.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:8f05d2d6a804fb70d3f4329d777ac62439773dcc2df827dd5f42644b10bf1fea", size = 13117353, upload-time = "2025-10-14T15:19:49.014Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/2e/8c83ec45a9365b4256495ca55eea30da7f03b02177b6da423c7da1ff5f6a/onnxruntime_migraphx-1.24.2-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ec814818da952bda3062e26f56c88bb713c00491ef91f86716c8d7346f9bc31b", size = 20341883, upload-time = "2026-02-21T07:25:17.86Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/42/0c/8d97419dfeedf419c5fe5293f3dbc59284855a63ad22e71f46c0010c9dc4/onnxruntime_openvino-1.23.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b963ea19bf9856f3d6b2f719d451f2eeae482a8f69c729906465aa4f27f4d39c", size = 70483359, upload-time = "2025-10-14T15:19:52.88Z" },
|
{ url = "https://files.pythonhosted.org/packages/9f/52/4776ac68dbc46ca02c9a14cc9e5c496017f47a18cedf606cc38f4911b96a/onnxruntime_migraphx-1.24.2-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:20e497538362170af639b03a40249d7ed61b873ac354f20d732b90252206e320", size = 20342422, upload-time = "2026-02-21T07:25:22.526Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/30/ff6111b16ffb4187c462824aa4e95acc20fdd90f856d44a339d56c6dacd6/onnxruntime_openvino-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:937e52657f94c56990a6e5bd4c3705bd6e970834c7c94e23d300dde6848f2889", size = 13117933, upload-time = "2025-10-14T15:19:58.319Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/44/db9035204a3363f9c0a4822c68e9a7520c13ef8d261f96b89b1375106dab/onnxruntime_migraphx-1.24.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:9d7f1b1a2b9651143a2080b4f42ee99eead02023de1855d1b8a02199a9c179aa", size = 20343783, upload-time = "2026-02-21T07:25:29.155Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/48/e42f618a8ec5fcf825fed4fdc8125f7105256cc6020b84567ecb88d5e2b7/onnxruntime_openvino-1.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2e93b9a8323e196b7433866054a59260f2206ab6fb0e7223dda91da71f1db8c5", size = 70483088, upload-time = "2025-10-14T15:20:02.425Z" },
|
]
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/f9/a531dc497dc113dc14df9a9de5aacb1676cadebc3ec6cc7cd3ca65cb3db0/onnxruntime_openvino-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ebbf70929de4ce269371cb255536bbedef588932d744da0b40e66c38a620f35", size = 13118206, upload-time = "2025-10-14T15:20:05.587Z" },
|
|
||||||
|
[[package]]
|
||||||
|
name = "onnxruntime-openvino"
|
||||||
|
version = "1.24.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "flatbuffers" },
|
||||||
|
{ name = "numpy" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "protobuf" },
|
||||||
|
{ name = "sympy" },
|
||||||
|
]
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/16/69ca742f0b65c40d4de3ff44bb6abc23c47b23e932bc901116176ae69922/onnxruntime_openvino-1.24.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:3007c803634cc69c6d52af1dea7ce729d9bb62b9a11070fd2f959119199007a8", size = 84430935, upload-time = "2026-02-26T13:44:32.193Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/73/619bb416bbfc40aebdd493fd6800d2637359294fe683d8a6bae3ff8d869a/onnxruntime_openvino-1.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:8042698232bf67f1f6b219c2b07728d7ae7ddff17d8524588de3675480609aef", size = 13655357, upload-time = "2026-02-26T13:44:35.555Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/cf/17ba72de2df0fcba349937d2788f154397bbc2d1a2d67772a97e26f6bc5f/onnxruntime_openvino-1.24.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d617fac2f59a6ab5ea59a788c3e1592240a129642519aaeaa774761dfe35150e", size = 84433207, upload-time = "2026-02-26T13:44:41.395Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/37/d301f2c68b19a9485ed5db3047e0fb52478f3e73eb08c7d2a7c61be7cc1c/onnxruntime_openvino-1.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:f186335a9c9b255633275290da7521d3d4d14c7773fee3127bfa040234d3fa5a", size = 13658075, upload-time = "2026-02-26T13:44:44.905Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/07/f225999919f56506b603aaa3ff837ad563ab26f86906ed7fa7e5abcd849e/onnxruntime_openvino-1.24.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:2c3bb73e68ac27f4891af8a595c1faf574ec68b772e6583c90a0b997a1822782", size = 84433183, upload-time = "2026-02-26T13:44:50.254Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/92/46ae2cd565961a89189900f385bb2f13a9fa731ea4674001d23720fbb1e0/onnxruntime_openvino-1.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:434bf49aa71393c577a456c9d76c98e6d6958a833fa0876793e3d5437b5a511a", size = 13658485, upload-time = "2026-02-26T13:44:53.889Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2173,15 +2179,6 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" },
|
{ url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyreadline3"
|
|
||||||
version = "3.4.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/86/3d61a61f36a0067874a00cb4dceb9028d34b6060e47828f7fc86fb9f7ee9/pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae", size = 86465, upload-time = "2022-01-24T20:05:11.66Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/56/fc/a3c13ded7b3057680c8ae95a9b6cc83e63657c38e0005c400a5d018a33a7/pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb", size = 95203, upload-time = "2022-01-24T20:05:10.442Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.2"
|
version = "9.0.2"
|
||||||
|
|||||||
11
mise.toml
11
mise.toml
@@ -14,15 +14,15 @@ config_roots = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "24.13.0"
|
node = "24.13.1"
|
||||||
flutter = "3.35.7"
|
flutter = "3.35.7"
|
||||||
pnpm = "10.28.2"
|
pnpm = "10.30.0"
|
||||||
terragrunt = "0.98.0"
|
terragrunt = "0.98.0"
|
||||||
opentofu = "1.11.4"
|
opentofu = "1.11.4"
|
||||||
java = "21.0.2"
|
java = "21.0.2"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"]
|
[tools."github:CQLabs/homebrew-dcm"]
|
||||||
version = "1.30.0"
|
version = "1.35.1"
|
||||||
bin = "dcm"
|
bin = "dcm"
|
||||||
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
||||||
|
|
||||||
@@ -37,13 +37,12 @@ run = "pnpm install --filter @immich/sdk --frozen-lockfile"
|
|||||||
|
|
||||||
[tasks."sdk:build"]
|
[tasks."sdk:build"]
|
||||||
dir = "open-api/typescript-sdk"
|
dir = "open-api/typescript-sdk"
|
||||||
env._.path = "./node_modules/.bin"
|
run = "pnpm run build"
|
||||||
run = "tsc"
|
|
||||||
|
|
||||||
# i18n tasks
|
# i18n tasks
|
||||||
[tasks."i18n:format"]
|
[tasks."i18n:format"]
|
||||||
dir = "i18n"
|
dir = "i18n"
|
||||||
run = { task = ":i18n:format-fix" }
|
run = "pnpm run format"
|
||||||
|
|
||||||
[tasks."i18n:format-fix"]
|
[tasks."i18n:format-fix"]
|
||||||
dir = "i18n"
|
dir = "i18n"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ private open class LocalImagesPigeonCodec : StandardMessageCodec() {
|
|||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface LocalImageApi {
|
interface LocalImageApi {
|
||||||
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
||||||
fun cancelRequest(requestId: Long)
|
fun cancelRequest(requestId: Long)
|
||||||
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
fun getThumbhash(thumbhash: String, callback: (Result<Map<String, Long>>) -> Unit)
|
||||||
|
|
||||||
@@ -82,7 +82,8 @@ interface LocalImageApi {
|
|||||||
val widthArg = args[2] as Long
|
val widthArg = args[2] as Long
|
||||||
val heightArg = args[3] as Long
|
val heightArg = args[3] as Long
|
||||||
val isVideoArg = args[4] as Boolean
|
val isVideoArg = args[4] as Boolean
|
||||||
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg) { result: Result<Map<String, Long>?> ->
|
val preferEncodedArg = args[5] as Boolean
|
||||||
|
api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
|
||||||
val error = result.exceptionOrNull()
|
val error = result.exceptionOrNull()
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
reply.reply(LocalImagesPigeonUtils.wrapError(error))
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.util.Size
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import app.alextran.immich.NativeBuffer
|
import app.alextran.immich.NativeBuffer
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
import java.io.IOException
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.Priority
|
import com.bumptech.glide.Priority
|
||||||
@@ -48,7 +49,6 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
|
|||||||
try {
|
try {
|
||||||
val buffer = NativeBuffer.wrap(pointer, size)
|
val buffer = NativeBuffer.wrap(pointer, size)
|
||||||
copyPixelsToBuffer(buffer)
|
copyPixelsToBuffer(buffer)
|
||||||
recycle()
|
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"pointer" to pointer,
|
"pointer" to pointer,
|
||||||
"width" to width.toLong(),
|
"width" to width.toLong(),
|
||||||
@@ -57,8 +57,9 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
|
|||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
NativeBuffer.free(pointer)
|
NativeBuffer.free(pointer)
|
||||||
recycle()
|
|
||||||
throw e
|
throw e
|
||||||
|
} finally {
|
||||||
|
recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,12 +100,17 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
|||||||
width: Long,
|
width: Long,
|
||||||
height: Long,
|
height: Long,
|
||||||
isVideo: Boolean,
|
isVideo: Boolean,
|
||||||
|
preferEncoded: Boolean,
|
||||||
callback: (Result<Map<String, Long>?>) -> Unit
|
callback: (Result<Map<String, Long>?>) -> Unit
|
||||||
) {
|
) {
|
||||||
val signal = CancellationSignal()
|
val signal = CancellationSignal()
|
||||||
val task = threadPool.submit {
|
val task = threadPool.submit {
|
||||||
try {
|
try {
|
||||||
getThumbnailBufferInternal(assetId, width, height, isVideo, callback, signal)
|
if (preferEncoded) {
|
||||||
|
getEncodedImageInternal(assetId, callback, signal)
|
||||||
|
} else {
|
||||||
|
getThumbnailBufferInternal(assetId, width, height, isVideo, callback, signal)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
when (e) {
|
when (e) {
|
||||||
is OperationCanceledException -> callback(CANCELLED)
|
is OperationCanceledException -> callback(CANCELLED)
|
||||||
@@ -133,6 +139,35 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getEncodedImageInternal(
|
||||||
|
assetId: String,
|
||||||
|
callback: (Result<Map<String, Long>?>) -> Unit,
|
||||||
|
signal: CancellationSignal
|
||||||
|
) {
|
||||||
|
signal.throwIfCanceled()
|
||||||
|
val id = assetId.toLong()
|
||||||
|
val uri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, id)
|
||||||
|
|
||||||
|
signal.throwIfCanceled()
|
||||||
|
val bytes = resolver.openInputStream(uri)?.use { it.readBytes() }
|
||||||
|
?: throw IOException("Could not read image data for $assetId")
|
||||||
|
|
||||||
|
signal.throwIfCanceled()
|
||||||
|
val pointer = NativeBuffer.allocate(bytes.size)
|
||||||
|
try {
|
||||||
|
val buffer = NativeBuffer.wrap(pointer, bytes.size)
|
||||||
|
buffer.put(bytes)
|
||||||
|
signal.throwIfCanceled()
|
||||||
|
callback(Result.success(mapOf(
|
||||||
|
"pointer" to pointer,
|
||||||
|
"length" to bytes.size.toLong()
|
||||||
|
)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
NativeBuffer.free(pointer)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getThumbnailBufferInternal(
|
private fun getThumbnailBufferInternal(
|
||||||
assetId: String,
|
assetId: String,
|
||||||
width: Long,
|
width: Long,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ private open class RemoteImagesPigeonCodec : StandardMessageCodec() {
|
|||||||
|
|
||||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||||
interface RemoteImageApi {
|
interface RemoteImageApi {
|
||||||
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, callback: (Result<Map<String, Long>?>) -> Unit)
|
fun requestImage(url: String, headers: Map<String, String>, requestId: Long, preferEncoded: Boolean, callback: (Result<Map<String, Long>?>) -> Unit)
|
||||||
fun cancelRequest(requestId: Long)
|
fun cancelRequest(requestId: Long)
|
||||||
fun clearCache(callback: (Result<Long>) -> Unit)
|
fun clearCache(callback: (Result<Long>) -> Unit)
|
||||||
|
|
||||||
@@ -68,7 +68,8 @@ interface RemoteImageApi {
|
|||||||
val urlArg = args[0] as String
|
val urlArg = args[0] as String
|
||||||
val headersArg = args[1] as Map<String, String>
|
val headersArg = args[1] as Map<String, String>
|
||||||
val requestIdArg = args[2] as Long
|
val requestIdArg = args[2] as Long
|
||||||
api.requestImage(urlArg, headersArg, requestIdArg) { result: Result<Map<String, Long>?> ->
|
val preferEncodedArg = args[3] as Boolean
|
||||||
|
api.requestImage(urlArg, headersArg, requestIdArg, preferEncodedArg) { result: Result<Map<String, Long>?> ->
|
||||||
val error = result.exceptionOrNull()
|
val error = result.exceptionOrNull()
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
|
reply.reply(RemoteImagesPigeonUtils.wrapError(error))
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
url: String,
|
url: String,
|
||||||
headers: Map<String, String>,
|
headers: Map<String, String>,
|
||||||
requestId: Long,
|
requestId: Long,
|
||||||
|
@Suppress("UNUSED_PARAMETER") preferEncoded: Boolean, // always returns encoded; setting has no effect on Android
|
||||||
callback: (Result<Map<String, Long>?>) -> Unit
|
callback: (Result<Map<String, Long>?>) -> Unit
|
||||||
) {
|
) {
|
||||||
val signal = CancellationSignal()
|
val signal = CancellationSignal()
|
||||||
|
|||||||
@@ -78,6 +78,21 @@ class FlutterError (
|
|||||||
val details: Any? = null
|
val details: Any? = null
|
||||||
) : Throwable()
|
) : Throwable()
|
||||||
|
|
||||||
|
enum class PlatformAssetPlaybackStyle(val raw: Int) {
|
||||||
|
UNKNOWN(0),
|
||||||
|
IMAGE(1),
|
||||||
|
VIDEO(2),
|
||||||
|
IMAGE_ANIMATED(3),
|
||||||
|
LIVE_PHOTO(4),
|
||||||
|
VIDEO_LOOPING(5);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun ofRaw(raw: Int): PlatformAssetPlaybackStyle? {
|
||||||
|
return values().firstOrNull { it.raw == raw }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Generated class from Pigeon that represents data sent in messages. */
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
data class PlatformAsset (
|
data class PlatformAsset (
|
||||||
val id: String,
|
val id: String,
|
||||||
@@ -92,7 +107,8 @@ data class PlatformAsset (
|
|||||||
val isFavorite: Boolean,
|
val isFavorite: Boolean,
|
||||||
val adjustmentTime: Long? = null,
|
val adjustmentTime: Long? = null,
|
||||||
val latitude: Double? = null,
|
val latitude: Double? = null,
|
||||||
val longitude: Double? = null
|
val longitude: Double? = null,
|
||||||
|
val playbackStyle: PlatformAssetPlaybackStyle
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
companion object {
|
companion object {
|
||||||
@@ -110,7 +126,8 @@ data class PlatformAsset (
|
|||||||
val adjustmentTime = pigeonVar_list[10] as Long?
|
val adjustmentTime = pigeonVar_list[10] as Long?
|
||||||
val latitude = pigeonVar_list[11] as Double?
|
val latitude = pigeonVar_list[11] as Double?
|
||||||
val longitude = pigeonVar_list[12] as Double?
|
val longitude = pigeonVar_list[12] as Double?
|
||||||
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude)
|
val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle
|
||||||
|
return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun toList(): List<Any?> {
|
fun toList(): List<Any?> {
|
||||||
@@ -128,6 +145,7 @@ data class PlatformAsset (
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
|
playbackStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -290,26 +308,31 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
129.toByte() -> {
|
129.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as Long?)?.let {
|
||||||
PlatformAsset.fromList(it)
|
PlatformAssetPlaybackStyle.ofRaw(it.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
130.toByte() -> {
|
130.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
PlatformAlbum.fromList(it)
|
PlatformAsset.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
131.toByte() -> {
|
131.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
SyncDelta.fromList(it)
|
PlatformAlbum.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
132.toByte() -> {
|
132.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
HashResult.fromList(it)
|
SyncDelta.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
133.toByte() -> {
|
133.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
HashResult.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
134.toByte() -> {
|
||||||
return (readValue(buffer) as? List<Any?>)?.let {
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
CloudIdResult.fromList(it)
|
CloudIdResult.fromList(it)
|
||||||
}
|
}
|
||||||
@@ -319,26 +342,30 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||||||
}
|
}
|
||||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||||
when (value) {
|
when (value) {
|
||||||
is PlatformAsset -> {
|
is PlatformAssetPlaybackStyle -> {
|
||||||
stream.write(129)
|
stream.write(129)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.raw)
|
||||||
}
|
}
|
||||||
is PlatformAlbum -> {
|
is PlatformAsset -> {
|
||||||
stream.write(130)
|
stream.write(130)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is SyncDelta -> {
|
is PlatformAlbum -> {
|
||||||
stream.write(131)
|
stream.write(131)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is HashResult -> {
|
is SyncDelta -> {
|
||||||
stream.write(132)
|
stream.write(132)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
is CloudIdResult -> {
|
is HashResult -> {
|
||||||
stream.write(133)
|
stream.write(133)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
|
is CloudIdResult -> {
|
||||||
|
stream.write(134)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
else -> super.writeValue(stream, value)
|
else -> super.writeValue(stream, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import app.alextran.immich.core.ImmichPlugin
|
import app.alextran.immich.core.ImmichPlugin
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.ImageHeaderParser
|
||||||
|
import com.bumptech.glide.load.ImageHeaderParserUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -28,6 +34,8 @@ sealed class AssetResult {
|
|||||||
data class InvalidAsset(val assetId: String) : AssetResult()
|
data class InvalidAsset(val assetId: String) : AssetResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val TAG = "NativeSyncApiImplBase"
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
||||||
private val ctx: Context = context.applicationContext
|
private val ctx: Context = context.applicationContext
|
||||||
@@ -39,6 +47,13 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS)
|
||||||
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED"
|
||||||
|
|
||||||
|
// MediaStore.Files.FileColumns.SPECIAL_FORMAT — S Extensions 21+
|
||||||
|
// https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT
|
||||||
|
private const val SPECIAL_FORMAT_COLUMN = "_special_format"
|
||||||
|
private const val SPECIAL_FORMAT_GIF = 1
|
||||||
|
private const val SPECIAL_FORMAT_MOTION_PHOTO = 2
|
||||||
|
private const val SPECIAL_FORMAT_ANIMATED_WEBP = 3
|
||||||
|
|
||||||
const val MEDIA_SELECTION =
|
const val MEDIA_SELECTION =
|
||||||
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
"(${MediaStore.Files.FileColumns.MEDIA_TYPE} = ? OR ${MediaStore.Files.FileColumns.MEDIA_TYPE} = ?)"
|
||||||
val MEDIA_SELECTION_ARGS = arrayOf(
|
val MEDIA_SELECTION_ARGS = arrayOf(
|
||||||
@@ -60,9 +75,15 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
add(MediaStore.MediaColumns.DURATION)
|
add(MediaStore.MediaColumns.DURATION)
|
||||||
add(MediaStore.MediaColumns.ORIENTATION)
|
add(MediaStore.MediaColumns.ORIENTATION)
|
||||||
// IS_FAVORITE is only available on Android 11 and above
|
// IS_FAVORITE is only available on Android 11 and above
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
add(MediaStore.MediaColumns.IS_FAVORITE)
|
add(MediaStore.MediaColumns.IS_FAVORITE)
|
||||||
}
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
add(SPECIAL_FORMAT_COLUMN)
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
// Fallback: read XMP from MediaStore to detect Motion Photos
|
||||||
|
add(MediaStore.MediaColumns.XMP)
|
||||||
|
}
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
|
const val HASH_BUFFER_SIZE = 2 * 1024 * 1024
|
||||||
@@ -109,9 +130,12 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
val orientationColumn =
|
val orientationColumn =
|
||||||
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
|
c.getColumnIndexOrThrow(MediaStore.MediaColumns.ORIENTATION)
|
||||||
val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE)
|
val favoriteColumn = c.getColumnIndex(MediaStore.MediaColumns.IS_FAVORITE)
|
||||||
|
val specialFormatColumn = c.getColumnIndex(SPECIAL_FORMAT_COLUMN)
|
||||||
|
val xmpColumn = c.getColumnIndex(MediaStore.MediaColumns.XMP)
|
||||||
|
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
val id = c.getLong(idColumn).toString()
|
val numericId = c.getLong(idColumn)
|
||||||
|
val id = numericId.toString()
|
||||||
val name = c.getStringOrNull(nameColumn)
|
val name = c.getStringOrNull(nameColumn)
|
||||||
val bucketId = c.getStringOrNull(bucketIdColumn)
|
val bucketId = c.getStringOrNull(bucketIdColumn)
|
||||||
val path = c.getStringOrNull(dataColumn)
|
val path = c.getStringOrNull(dataColumn)
|
||||||
@@ -125,10 +149,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaType = when (c.getInt(mediaTypeColumn)) {
|
val rawMediaType = c.getInt(mediaTypeColumn)
|
||||||
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1
|
val assetType: Long = when (rawMediaType) {
|
||||||
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2
|
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> 1L
|
||||||
else -> 0
|
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> 2L
|
||||||
|
else -> 0L
|
||||||
}
|
}
|
||||||
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
// Date taken is milliseconds since epoch, Date added is seconds since epoch
|
||||||
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
val createdAt = (c.getLong(dateTakenColumn).takeIf { it > 0 }?.div(1000))
|
||||||
@@ -138,15 +163,19 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
val width = c.getInt(widthColumn).toLong()
|
val width = c.getInt(widthColumn).toLong()
|
||||||
val height = c.getInt(heightColumn).toLong()
|
val height = c.getInt(heightColumn).toLong()
|
||||||
// Duration is milliseconds
|
// Duration is milliseconds
|
||||||
val duration = if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0
|
val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L
|
||||||
else c.getLong(durationColumn) / 1000
|
else c.getLong(durationColumn) / 1000
|
||||||
val orientation = c.getInt(orientationColumn)
|
val orientation = c.getInt(orientationColumn)
|
||||||
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
|
val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0
|
||||||
|
|
||||||
|
val playbackStyle = detectPlaybackStyle(
|
||||||
|
numericId, rawMediaType, specialFormatColumn, xmpColumn, c
|
||||||
|
)
|
||||||
|
|
||||||
val asset = PlatformAsset(
|
val asset = PlatformAsset(
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
mediaType.toLong(),
|
assetType,
|
||||||
createdAt,
|
createdAt,
|
||||||
modifiedAt,
|
modifiedAt,
|
||||||
width,
|
width,
|
||||||
@@ -154,6 +183,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
duration,
|
duration,
|
||||||
orientation.toLong(),
|
orientation.toLong(),
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
playbackStyle = playbackStyle,
|
||||||
)
|
)
|
||||||
yield(AssetResult.ValidAsset(asset, bucketId))
|
yield(AssetResult.ValidAsset(asset, bucketId))
|
||||||
}
|
}
|
||||||
@@ -161,6 +191,81 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the playback style for an asset using _special_format (API 33+)
|
||||||
|
* or XMP / MIME / RIFF header fallbacks (pre-33).
|
||||||
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
private fun detectPlaybackStyle(
|
||||||
|
assetId: Long,
|
||||||
|
rawMediaType: Int,
|
||||||
|
specialFormatColumn: Int,
|
||||||
|
xmpColumn: Int,
|
||||||
|
cursor: Cursor
|
||||||
|
): PlatformAssetPlaybackStyle {
|
||||||
|
// video currently has no special formats, so we can short circuit and avoid unnecessary work
|
||||||
|
if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) {
|
||||||
|
return PlatformAssetPlaybackStyle.VIDEO
|
||||||
|
}
|
||||||
|
|
||||||
|
// API 33+: use _special_format from cursor
|
||||||
|
if (specialFormatColumn != -1) {
|
||||||
|
val specialFormat = cursor.getInt(specialFormatColumn)
|
||||||
|
return when {
|
||||||
|
specialFormat == SPECIAL_FORMAT_MOTION_PHOTO -> PlatformAssetPlaybackStyle.LIVE_PHOTO
|
||||||
|
specialFormat == SPECIAL_FORMAT_GIF || specialFormat == SPECIAL_FORMAT_ANIMATED_WEBP -> PlatformAssetPlaybackStyle.IMAGE_ANIMATED
|
||||||
|
rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> PlatformAssetPlaybackStyle.IMAGE
|
||||||
|
else -> PlatformAssetPlaybackStyle.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawMediaType != MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) {
|
||||||
|
return PlatformAssetPlaybackStyle.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-API 33 fallback
|
||||||
|
val uri = ContentUris.withAppendedId(
|
||||||
|
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
|
||||||
|
assetId
|
||||||
|
)
|
||||||
|
|
||||||
|
// Read XMP from cursor (API 30+) or ExifInterface stream (pre-30)
|
||||||
|
val xmp: String? = if (xmpColumn != -1) {
|
||||||
|
cursor.getBlob(xmpColumn)?.toString(Charsets.UTF_8)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
ctx.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
|
ExifInterface(stream).getAttribute(ExifInterface.TAG_XMP)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Failed to read XMP for asset $assetId", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmp != null && "Camera:MotionPhoto" in xmp) {
|
||||||
|
return PlatformAssetPlaybackStyle.LIVE_PHOTO
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
|
val glide = Glide.get(ctx)
|
||||||
|
val type = ImageHeaderParserUtils.getType(
|
||||||
|
glide.registry.imageHeaderParsers,
|
||||||
|
stream,
|
||||||
|
glide.arrayPool
|
||||||
|
)
|
||||||
|
if (type == ImageHeaderParser.ImageType.GIF || type == ImageHeaderParser.ImageType.ANIMATED_WEBP) {
|
||||||
|
return PlatformAssetPlaybackStyle.IMAGE_ANIMATED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Failed to parse image header for asset $assetId", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlatformAssetPlaybackStyle.IMAGE
|
||||||
|
}
|
||||||
|
|
||||||
fun getAlbums(): List<PlatformAlbum> {
|
fun getAlbums(): List<PlatformAlbum> {
|
||||||
val albums = mutableListOf<PlatformAlbum>()
|
val albums = mutableListOf<PlatformAlbum>()
|
||||||
val albumsCount = mutableMapOf<String, Int>()
|
val albumsCount = mutableMapOf<String, Int>()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
version: '>=1.29.0 <=1.30.0'
|
version: '>=1.29.0 <=1.36.0'
|
||||||
|
|||||||
1
mobile/drift_schemas/main/drift_schema_v20.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v20.json
generated
Normal file
File diff suppressed because one or more lines are too long
@@ -70,7 +70,7 @@ class LocalImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
|||||||
|
|
||||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||||
protocol LocalImageApi {
|
protocol LocalImageApi {
|
||||||
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
||||||
func cancelRequest(requestId: Int64) throws
|
func cancelRequest(requestId: Int64) throws
|
||||||
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void)
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,8 @@ class LocalImageApiSetup {
|
|||||||
let widthArg = args[2] as! Int64
|
let widthArg = args[2] as! Int64
|
||||||
let heightArg = args[3] as! Int64
|
let heightArg = args[3] as! Int64
|
||||||
let isVideoArg = args[4] as! Bool
|
let isVideoArg = args[4] as! Bool
|
||||||
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg) { result in
|
let preferEncodedArg = args[5] as! Bool
|
||||||
|
api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg, preferEncoded: preferEncodedArg) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let res):
|
case .success(let res):
|
||||||
reply(wrapResult(res))
|
reply(wrapResult(res))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class LocalImageRequest {
|
|||||||
weak var workItem: DispatchWorkItem?
|
weak var workItem: DispatchWorkItem?
|
||||||
var isCancelled = false
|
var isCancelled = false
|
||||||
let callback: (Result<[String: Int64]?, any Error>) -> Void
|
let callback: (Result<[String: Int64]?, any Error>) -> Void
|
||||||
|
|
||||||
init(callback: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
init(callback: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
}
|
}
|
||||||
@@ -30,11 +30,11 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
requestOptions.version = .current
|
requestOptions.version = .current
|
||||||
return requestOptions
|
return requestOptions
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private static let assetQueue = DispatchQueue(label: "thumbnail.assets", qos: .userInitiated)
|
private static let assetQueue = DispatchQueue(label: "thumbnail.assets", qos: .userInitiated)
|
||||||
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
|
private static let requestQueue = DispatchQueue(label: "thumbnail.requests", qos: .userInitiated)
|
||||||
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
|
private static let cancelQueue = DispatchQueue(label: "thumbnail.cancellation", qos: .default)
|
||||||
|
|
||||||
private static var rgbaFormat = vImage_CGImageFormat(
|
private static var rgbaFormat = vImage_CGImageFormat(
|
||||||
bitsPerComponent: 8,
|
bitsPerComponent: 8,
|
||||||
bitsPerPixel: 32,
|
bitsPerPixel: 32,
|
||||||
@@ -48,12 +48,12 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
assetCache.countLimit = 10000
|
assetCache.countLimit = 10000
|
||||||
return assetCache
|
return assetCache
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
|
func getThumbhash(thumbhash: String, completion: @escaping (Result<[String : Int64], any Error>) -> Void) {
|
||||||
ImageProcessing.queue.async {
|
ImageProcessing.queue.async {
|
||||||
guard let data = Data(base64Encoded: thumbhash)
|
guard let data = Data(base64Encoded: thumbhash)
|
||||||
else { return completion(.failure(PigeonError(code: "", message: "Invalid base64 string: \(thumbhash)", details: nil)))}
|
else { return completion(.failure(PigeonError(code: "", message: "Invalid base64 string: \(thumbhash)", details: nil)))}
|
||||||
|
|
||||||
let (width, height, pointer) = thumbHashToRGBA(hash: data)
|
let (width, height, pointer) = thumbHashToRGBA(hash: data)
|
||||||
completion(.success([
|
completion(.success([
|
||||||
"pointer": Int64(Int(bitPattern: pointer.baseAddress)),
|
"pointer": Int64(Int(bitPattern: pointer.baseAddress)),
|
||||||
@@ -63,34 +63,77 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
||||||
let request = LocalImageRequest(callback: completion)
|
let request = LocalImageRequest(callback: completion)
|
||||||
let item = DispatchWorkItem {
|
let item = DispatchWorkItem {
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageProcessing.semaphore.wait()
|
ImageProcessing.semaphore.wait()
|
||||||
defer {
|
defer {
|
||||||
ImageProcessing.semaphore.signal()
|
ImageProcessing.semaphore.signal()
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let asset = Self.requestAsset(assetId: assetId)
|
guard let asset = Self.requestAsset(assetId: assetId)
|
||||||
else {
|
else {
|
||||||
Self.remove(requestId: requestId)
|
Self.remove(requestId: requestId)
|
||||||
completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil)))
|
completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if preferEncoded {
|
||||||
|
let dataOptions = PHImageRequestOptions()
|
||||||
|
dataOptions.isNetworkAccessAllowed = true
|
||||||
|
dataOptions.isSynchronous = true
|
||||||
|
dataOptions.version = .current
|
||||||
|
|
||||||
|
var imageData: Data?
|
||||||
|
Self.imageManager.requestImageDataAndOrientation(
|
||||||
|
for: asset,
|
||||||
|
options: dataOptions,
|
||||||
|
resultHandler: { (data, _, _, _) in
|
||||||
|
imageData = data
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if request.isCancelled {
|
||||||
|
Self.remove(requestId: requestId)
|
||||||
|
return completion(ImageProcessing.cancelledResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = imageData else {
|
||||||
|
Self.remove(requestId: requestId)
|
||||||
|
return completion(.failure(PigeonError(code: "", message: "Could not get image data for \(assetId)", details: nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let length = data.count
|
||||||
|
let pointer = malloc(length)!
|
||||||
|
data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length)
|
||||||
|
|
||||||
|
if request.isCancelled {
|
||||||
|
free(pointer)
|
||||||
|
Self.remove(requestId: requestId)
|
||||||
|
return completion(ImageProcessing.cancelledResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.callback(.success([
|
||||||
|
"pointer": Int64(Int(bitPattern: pointer)),
|
||||||
|
"length": Int64(length),
|
||||||
|
]))
|
||||||
|
Self.remove(requestId: requestId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
Self.imageManager.requestImage(
|
Self.imageManager.requestImage(
|
||||||
for: asset,
|
for: asset,
|
||||||
@@ -101,29 +144,29 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
image = _image
|
image = _image
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let image = image,
|
guard let image = image,
|
||||||
let cgImage = image.cgImage else {
|
let cgImage = image.cgImage else {
|
||||||
Self.remove(requestId: requestId)
|
Self.remove(requestId: requestId)
|
||||||
return completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
|
return completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let buffer = try vImage_Buffer(cgImage: cgImage, format: Self.rgbaFormat)
|
let buffer = try vImage_Buffer(cgImage: cgImage, format: Self.rgbaFormat)
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
buffer.free()
|
buffer.free()
|
||||||
return completion(ImageProcessing.cancelledResult)
|
return completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.callback(.success([
|
request.callback(.success([
|
||||||
"pointer": Int64(Int(bitPattern: buffer.data)),
|
"pointer": Int64(Int(bitPattern: buffer.data)),
|
||||||
"width": Int64(buffer.width),
|
"width": Int64(buffer.width),
|
||||||
@@ -136,24 +179,24 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
return completion(.failure(PigeonError(code: "", message: "Failed to convert image for \(assetId): \(error)", details: nil)))
|
return completion(.failure(PigeonError(code: "", message: "Failed to convert image for \(assetId): \(error)", details: nil)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.workItem = item
|
request.workItem = item
|
||||||
Self.add(requestId: requestId, request: request)
|
Self.add(requestId: requestId, request: request)
|
||||||
ImageProcessing.queue.async(execute: item)
|
ImageProcessing.queue.async(execute: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelRequest(requestId: Int64) {
|
func cancelRequest(requestId: Int64) {
|
||||||
Self.cancel(requestId: requestId)
|
Self.cancel(requestId: requestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func add(requestId: Int64, request: LocalImageRequest) -> Void {
|
private static func add(requestId: Int64, request: LocalImageRequest) -> Void {
|
||||||
requestQueue.sync { requests[requestId] = request }
|
requestQueue.sync { requests[requestId] = request }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func remove(requestId: Int64) -> Void {
|
private static func remove(requestId: Int64) -> Void {
|
||||||
requestQueue.sync { requests[requestId] = nil }
|
requestQueue.sync { requests[requestId] = nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func cancel(requestId: Int64) -> Void {
|
private static func cancel(requestId: Int64) -> Void {
|
||||||
requestQueue.async {
|
requestQueue.async {
|
||||||
guard let request = requests.removeValue(forKey: requestId) else { return }
|
guard let request = requests.removeValue(forKey: requestId) else { return }
|
||||||
@@ -164,12 +207,12 @@ class LocalImageApiImpl: LocalImageApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func requestAsset(assetId: String) -> PHAsset? {
|
private static func requestAsset(assetId: String) -> PHAsset? {
|
||||||
var asset: PHAsset?
|
var asset: PHAsset?
|
||||||
assetQueue.sync { asset = assetCache.object(forKey: assetId as NSString) }
|
assetQueue.sync { asset = assetCache.object(forKey: assetId as NSString) }
|
||||||
if asset != nil { return asset }
|
if asset != nil { return asset }
|
||||||
|
|
||||||
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: Self.fetchOptions).firstObject
|
guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: Self.fetchOptions).firstObject
|
||||||
else { return nil }
|
else { return nil }
|
||||||
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
assetQueue.async { assetCache.setObject(asset, forKey: assetId as NSString) }
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable
|
|||||||
|
|
||||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||||
protocol RemoteImageApi {
|
protocol RemoteImageApi {
|
||||||
func requestImage(url: String, headers: [String: String], requestId: Int64, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
func requestImage(url: String, headers: [String: String], requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void)
|
||||||
func cancelRequest(requestId: Int64) throws
|
func cancelRequest(requestId: Int64) throws
|
||||||
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
|
func clearCache(completion: @escaping (Result<Int64, Error>) -> Void)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,8 @@ class RemoteImageApiSetup {
|
|||||||
let urlArg = args[0] as! String
|
let urlArg = args[0] as! String
|
||||||
let headersArg = args[1] as! [String: String]
|
let headersArg = args[1] as! [String: String]
|
||||||
let requestIdArg = args[2] as! Int64
|
let requestIdArg = args[2] as! Int64
|
||||||
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg) { result in
|
let preferEncodedArg = args[3] as! Bool
|
||||||
|
api.requestImage(url: urlArg, headers: headersArg, requestId: requestIdArg, preferEncoded: preferEncodedArg) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let res):
|
case .success(let res):
|
||||||
reply(wrapResult(res))
|
reply(wrapResult(res))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class RemoteImageRequest {
|
|||||||
let id: Int64
|
let id: Int64
|
||||||
var isCancelled = false
|
var isCancelled = false
|
||||||
let completion: (Result<[String: Int64]?, any Error>) -> Void
|
let completion: (Result<[String: Int64]?, any Error>) -> Void
|
||||||
|
|
||||||
init(id: Int64, task: URLSessionDataTask, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
init(id: Int64, task: URLSessionDataTask, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.task = task
|
self.task = task
|
||||||
@@ -32,75 +32,93 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||||
kCGImageSourceCreateThumbnailFromImageAlways: true
|
kCGImageSourceCreateThumbnailFromImageAlways: true
|
||||||
] as CFDictionary
|
] as CFDictionary
|
||||||
|
|
||||||
func requestImage(url: String, headers: [String : String], requestId: Int64, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
|
func requestImage(url: String, headers: [String : String], requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) {
|
||||||
var urlRequest = URLRequest(url: URL(string: url)!)
|
var urlRequest = URLRequest(url: URL(string: url)!)
|
||||||
urlRequest.cachePolicy = .returnCacheDataElseLoad
|
urlRequest.cachePolicy = .returnCacheDataElseLoad
|
||||||
for (key, value) in headers {
|
for (key, value) in headers {
|
||||||
urlRequest.setValue(value, forHTTPHeaderField: key)
|
urlRequest.setValue(value, forHTTPHeaderField: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in
|
let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in
|
||||||
Self.handleCompletion(requestId: requestId, data: data, response: response, error: error)
|
Self.handleCompletion(requestId: requestId, encoded: preferEncoded, data: data, response: response, error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = RemoteImageRequest(id: requestId, task: task, completion: completion)
|
let request = RemoteImageRequest(id: requestId, task: task, completion: completion)
|
||||||
|
|
||||||
os_unfair_lock_lock(&Self.lock)
|
os_unfair_lock_lock(&Self.lock)
|
||||||
Self.requests[requestId] = request
|
Self.requests[requestId] = request
|
||||||
os_unfair_lock_unlock(&Self.lock)
|
os_unfair_lock_unlock(&Self.lock)
|
||||||
|
|
||||||
task.resume()
|
task.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func handleCompletion(requestId: Int64, data: Data?, response: URLResponse?, error: Error?) {
|
private static func handleCompletion(requestId: Int64, encoded: Bool, data: Data?, response: URLResponse?, error: Error?) {
|
||||||
os_unfair_lock_lock(&Self.lock)
|
os_unfair_lock_lock(&Self.lock)
|
||||||
guard let request = requests[requestId] else {
|
guard let request = requests[requestId] else {
|
||||||
return os_unfair_lock_unlock(&Self.lock)
|
return os_unfair_lock_unlock(&Self.lock)
|
||||||
}
|
}
|
||||||
requests[requestId] = nil
|
requests[requestId] = nil
|
||||||
os_unfair_lock_unlock(&Self.lock)
|
os_unfair_lock_unlock(&Self.lock)
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
if request.isCancelled || (error as NSError).code == NSURLErrorCancelled {
|
if request.isCancelled || (error as NSError).code == NSURLErrorCancelled {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
return request.completion(.failure(error))
|
return request.completion(.failure(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let data = data else {
|
guard let data = data else {
|
||||||
return request.completion(.failure(PigeonError(code: "", message: "No data received", details: nil)))
|
return request.completion(.failure(PigeonError(code: "", message: "No data received", details: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageProcessing.queue.async {
|
ImageProcessing.queue.async {
|
||||||
ImageProcessing.semaphore.wait()
|
ImageProcessing.semaphore.wait()
|
||||||
defer { ImageProcessing.semaphore.signal() }
|
defer { ImageProcessing.semaphore.signal() }
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return raw encoded bytes when requested (for animated images)
|
||||||
|
if encoded {
|
||||||
|
let length = data.count
|
||||||
|
let pointer = malloc(length)!
|
||||||
|
data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length)
|
||||||
|
|
||||||
|
if request.isCancelled {
|
||||||
|
free(pointer)
|
||||||
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.completion(
|
||||||
|
.success([
|
||||||
|
"pointer": Int64(Int(bitPattern: pointer)),
|
||||||
|
"length": Int64(length),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
|
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil),
|
||||||
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
|
let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else {
|
||||||
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
|
return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let buffer = try vImage_Buffer(cgImage: cgImage, format: rgbaFormat)
|
let buffer = try vImage_Buffer(cgImage: cgImage, format: rgbaFormat)
|
||||||
|
|
||||||
if request.isCancelled {
|
if request.isCancelled {
|
||||||
buffer.free()
|
buffer.free()
|
||||||
return request.completion(ImageProcessing.cancelledResult)
|
return request.completion(ImageProcessing.cancelledResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.completion(
|
request.completion(
|
||||||
.success([
|
.success([
|
||||||
"pointer": Int64(Int(bitPattern: buffer.data)),
|
"pointer": Int64(Int(bitPattern: buffer.data)),
|
||||||
@@ -113,17 +131,17 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancelRequest(requestId: Int64) {
|
func cancelRequest(requestId: Int64) {
|
||||||
os_unfair_lock_lock(&Self.lock)
|
os_unfair_lock_lock(&Self.lock)
|
||||||
let request = Self.requests[requestId]
|
let request = Self.requests[requestId]
|
||||||
os_unfair_lock_unlock(&Self.lock)
|
os_unfair_lock_unlock(&Self.lock)
|
||||||
|
|
||||||
guard let request = request else { return }
|
guard let request = request else { return }
|
||||||
request.isCancelled = true
|
request.isCancelled = true
|
||||||
request.task?.cancel()
|
request.task?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearCache(completion: @escaping (Result<Int64, any Error>) -> Void) {
|
func clearCache(completion: @escaping (Result<Int64, any Error>) -> Void) {
|
||||||
Task {
|
Task {
|
||||||
let cache = URLSessionManager.shared.session.configuration.urlCache!
|
let cache = URLSessionManager.shared.session.configuration.urlCache!
|
||||||
|
|||||||
@@ -128,6 +128,15 @@ func deepHashMessages(value: Any?, hasher: inout Hasher) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enum PlatformAssetPlaybackStyle: Int {
|
||||||
|
case unknown = 0
|
||||||
|
case image = 1
|
||||||
|
case video = 2
|
||||||
|
case imageAnimated = 3
|
||||||
|
case livePhoto = 4
|
||||||
|
case videoLooping = 5
|
||||||
|
}
|
||||||
|
|
||||||
/// Generated class from Pigeon that represents data sent in messages.
|
/// Generated class from Pigeon that represents data sent in messages.
|
||||||
struct PlatformAsset: Hashable {
|
struct PlatformAsset: Hashable {
|
||||||
var id: String
|
var id: String
|
||||||
@@ -143,6 +152,7 @@ struct PlatformAsset: Hashable {
|
|||||||
var adjustmentTime: Int64? = nil
|
var adjustmentTime: Int64? = nil
|
||||||
var latitude: Double? = nil
|
var latitude: Double? = nil
|
||||||
var longitude: Double? = nil
|
var longitude: Double? = nil
|
||||||
|
var playbackStyle: PlatformAssetPlaybackStyle
|
||||||
|
|
||||||
|
|
||||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
@@ -160,6 +170,7 @@ struct PlatformAsset: Hashable {
|
|||||||
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
|
let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10])
|
||||||
let latitude: Double? = nilOrValue(pigeonVar_list[11])
|
let latitude: Double? = nilOrValue(pigeonVar_list[11])
|
||||||
let longitude: Double? = nilOrValue(pigeonVar_list[12])
|
let longitude: Double? = nilOrValue(pigeonVar_list[12])
|
||||||
|
let playbackStyle = pigeonVar_list[13] as! PlatformAssetPlaybackStyle
|
||||||
|
|
||||||
return PlatformAsset(
|
return PlatformAsset(
|
||||||
id: id,
|
id: id,
|
||||||
@@ -174,7 +185,8 @@ struct PlatformAsset: Hashable {
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude
|
longitude: longitude,
|
||||||
|
playbackStyle: playbackStyle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
func toList() -> [Any?] {
|
func toList() -> [Any?] {
|
||||||
@@ -192,6 +204,7 @@ struct PlatformAsset: Hashable {
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
|
playbackStyle,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool {
|
||||||
@@ -349,14 +362,20 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
|||||||
override func readValue(ofType type: UInt8) -> Any? {
|
override func readValue(ofType type: UInt8) -> Any? {
|
||||||
switch type {
|
switch type {
|
||||||
case 129:
|
case 129:
|
||||||
return PlatformAsset.fromList(self.readValue() as! [Any?])
|
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
|
||||||
|
if let enumResultAsInt = enumResultAsInt {
|
||||||
|
return PlatformAssetPlaybackStyle(rawValue: enumResultAsInt)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case 130:
|
case 130:
|
||||||
return PlatformAlbum.fromList(self.readValue() as! [Any?])
|
return PlatformAsset.fromList(self.readValue() as! [Any?])
|
||||||
case 131:
|
case 131:
|
||||||
return SyncDelta.fromList(self.readValue() as! [Any?])
|
return PlatformAlbum.fromList(self.readValue() as! [Any?])
|
||||||
case 132:
|
case 132:
|
||||||
return HashResult.fromList(self.readValue() as! [Any?])
|
return SyncDelta.fromList(self.readValue() as! [Any?])
|
||||||
case 133:
|
case 133:
|
||||||
|
return HashResult.fromList(self.readValue() as! [Any?])
|
||||||
|
case 134:
|
||||||
return CloudIdResult.fromList(self.readValue() as! [Any?])
|
return CloudIdResult.fromList(self.readValue() as! [Any?])
|
||||||
default:
|
default:
|
||||||
return super.readValue(ofType: type)
|
return super.readValue(ofType: type)
|
||||||
@@ -366,21 +385,24 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
|||||||
|
|
||||||
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
||||||
override func writeValue(_ value: Any) {
|
override func writeValue(_ value: Any) {
|
||||||
if let value = value as? PlatformAsset {
|
if let value = value as? PlatformAssetPlaybackStyle {
|
||||||
super.writeByte(129)
|
super.writeByte(129)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.rawValue)
|
||||||
} else if let value = value as? PlatformAlbum {
|
} else if let value = value as? PlatformAsset {
|
||||||
super.writeByte(130)
|
super.writeByte(130)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? SyncDelta {
|
} else if let value = value as? PlatformAlbum {
|
||||||
super.writeByte(131)
|
super.writeByte(131)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? HashResult {
|
} else if let value = value as? SyncDelta {
|
||||||
super.writeByte(132)
|
super.writeByte(132)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
} else if let value = value as? CloudIdResult {
|
} else if let value = value as? HashResult {
|
||||||
super.writeByte(133)
|
super.writeByte(133)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
|
} else if let value = value as? CloudIdResult {
|
||||||
|
super.writeByte(134)
|
||||||
|
super.writeValue(value.toList())
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(value)
|
super.writeValue(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,8 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
type: 0,
|
type: 0,
|
||||||
durationInSeconds: 0,
|
durationInSeconds: 0,
|
||||||
orientation: 0,
|
orientation: 0,
|
||||||
isFavorite: false
|
isFavorite: false,
|
||||||
|
playbackStyle: .unknown
|
||||||
)
|
)
|
||||||
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
|
if (updatedAssets.contains(AssetWrapper(with: predicate))) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
import Photos
|
import Photos
|
||||||
|
|
||||||
extension PHAsset {
|
extension PHAsset {
|
||||||
|
var platformPlaybackStyle: PlatformAssetPlaybackStyle {
|
||||||
|
switch playbackStyle {
|
||||||
|
case .image: return .image
|
||||||
|
case .imageAnimated: return .imageAnimated
|
||||||
|
case .livePhoto: return .livePhoto
|
||||||
|
case .video: return .video
|
||||||
|
case .videoLooping: return .videoLooping
|
||||||
|
@unknown default: return .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toPlatformAsset() -> PlatformAsset {
|
func toPlatformAsset() -> PlatformAsset {
|
||||||
return PlatformAsset(
|
return PlatformAsset(
|
||||||
id: localIdentifier,
|
id: localIdentifier,
|
||||||
@@ -15,7 +26,8 @@ extension PHAsset {
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
adjustmentTime: adjustmentTimestamp,
|
adjustmentTime: adjustmentTimestamp,
|
||||||
latitude: location?.coordinate.latitude,
|
latitude: location?.coordinate.latitude,
|
||||||
longitude: location?.coordinate.longitude
|
longitude: location?.coordinate.longitude,
|
||||||
|
playbackStyle: platformPlaybackStyle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +38,7 @@ extension PHAsset {
|
|||||||
var filename: String? {
|
var filename: String? {
|
||||||
return value(forKey: "filename") as? String
|
return value(forKey: "filename") as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
var adjustmentTimestamp: Int64? {
|
var adjustmentTimestamp: Int64? {
|
||||||
if let date = value(forKey: "adjustmentTimestamp") as? Date {
|
if let date = value(forKey: "adjustmentTimestamp") as? Date {
|
||||||
return Int64(date.timeIntervalSince1970)
|
return Int64(date.timeIntervalSince1970)
|
||||||
|
|||||||
@@ -18,3 +18,5 @@ enum ActionSource { timeline, viewer }
|
|||||||
enum CleanupStep { selectDate, scan, delete }
|
enum CleanupStep { selectDate, scan, delete }
|
||||||
|
|
||||||
enum AssetKeepType { none, photosOnly, videosOnly }
|
enum AssetKeepType { none, photosOnly, videosOnly }
|
||||||
|
|
||||||
|
enum AssetDateAggregation { start, end }
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ class ScrollToDateEvent extends Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Asset Viewer Events
|
// Asset Viewer Events
|
||||||
class ViewerOpenBottomSheetEvent extends Event {
|
class ViewerShowDetailsEvent extends Event {
|
||||||
final bool activitiesMode;
|
const ViewerShowDetailsEvent();
|
||||||
const ViewerOpenBottomSheetEvent({this.activitiesMode = false});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewerReloadAssetEvent extends Event {
|
class ViewerReloadAssetEvent extends Event {
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ enum StoreKey<T> {
|
|||||||
autoPlayVideo<bool>._(139),
|
autoPlayVideo<bool>._(139),
|
||||||
albumGridView<bool>._(140),
|
albumGridView<bool>._(140),
|
||||||
|
|
||||||
|
// Image viewer navigation settings
|
||||||
|
tapToNavigate<bool>._(141),
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000),
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
betaPromptShown<bool>._(1001),
|
betaPromptShown<bool>._(1001),
|
||||||
|
|||||||
29
mobile/lib/domain/models/tag.model.dart
Normal file
29
mobile/lib/domain/models/tag.model.dart
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class Tag {
|
||||||
|
final String id;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
const Tag({required this.id, required this.value});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Tag(id: $id, value: $value)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant Tag other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.id == id && other.value == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return id.hashCode ^ value.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Tag fromDto(TagResponseDto dto) {
|
||||||
|
return Tag(id: dto.id, value: dto.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,8 +43,8 @@ class RemoteAlbumService {
|
|||||||
AlbumSortMode.title => albums.sortedBy((album) => album.name),
|
AlbumSortMode.title => albums.sortedBy((album) => album.name),
|
||||||
AlbumSortMode.lastModified => albums.sortedBy((album) => album.updatedAt),
|
AlbumSortMode.lastModified => albums.sortedBy((album) => album.updatedAt),
|
||||||
AlbumSortMode.assetCount => albums.sortedBy((album) => album.assetCount),
|
AlbumSortMode.assetCount => albums.sortedBy((album) => album.assetCount),
|
||||||
AlbumSortMode.mostRecent => await _sortByNewestAsset(albums),
|
AlbumSortMode.mostRecent => await _sortByAssetDate(albums, aggregation: AssetDateAggregation.end),
|
||||||
AlbumSortMode.mostOldest => await _sortByOldestAsset(albums),
|
AlbumSortMode.mostOldest => await _sortByAssetDate(albums, aggregation: AssetDateAggregation.start),
|
||||||
};
|
};
|
||||||
final effectiveOrder = isReverse ? sortMode.defaultOrder.reverse() : sortMode.defaultOrder;
|
final effectiveOrder = isReverse ? sortMode.defaultOrder.reverse() : sortMode.defaultOrder;
|
||||||
|
|
||||||
@@ -172,46 +172,25 @@ class RemoteAlbumService {
|
|||||||
return _repository.getAlbumsContainingAsset(assetId);
|
return _repository.getAlbumsContainingAsset(assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<RemoteAlbum>> _sortByNewestAsset(List<RemoteAlbum> albums) async {
|
Future<List<RemoteAlbum>> _sortByAssetDate(
|
||||||
// map album IDs to their newest asset dates
|
List<RemoteAlbum> albums, {
|
||||||
final Map<String, Future<DateTime?>> assetTimestampFutures = {};
|
required AssetDateAggregation aggregation,
|
||||||
for (final album in albums) {
|
}) async {
|
||||||
assetTimestampFutures[album.id] = _repository.getNewestAssetTimestamp(album.id);
|
if (albums.isEmpty) return [];
|
||||||
|
|
||||||
|
final albumIds = albums.map((e) => e.id).toList();
|
||||||
|
final sortedIds = await _repository.getSortedAlbumIds(albumIds, aggregation: aggregation);
|
||||||
|
|
||||||
|
final albumMap = Map<String, RemoteAlbum>.fromEntries(albums.map((a) => MapEntry(a.id, a)));
|
||||||
|
|
||||||
|
final sortedAlbums = sortedIds.map((id) => albumMap[id]).whereType<RemoteAlbum>().toList();
|
||||||
|
|
||||||
|
if (sortedAlbums.length < albums.length) {
|
||||||
|
final returnedIdSet = sortedIds.toSet();
|
||||||
|
final emptyAlbums = albums.where((a) => !returnedIdSet.contains(a.id));
|
||||||
|
sortedAlbums.addAll(emptyAlbums);
|
||||||
}
|
}
|
||||||
|
|
||||||
// await all database queries
|
return sortedAlbums;
|
||||||
final entries = await Future.wait(
|
|
||||||
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
|
|
||||||
);
|
|
||||||
final assetTimestamps = Map.fromEntries(entries);
|
|
||||||
|
|
||||||
final sorted = albums.sorted((a, b) {
|
|
||||||
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
|
||||||
final bDate = assetTimestamps[b.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
|
||||||
return aDate.compareTo(bDate);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sorted;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<RemoteAlbum>> _sortByOldestAsset(List<RemoteAlbum> albums) async {
|
|
||||||
// map album IDs to their oldest asset dates
|
|
||||||
final Map<String, Future<DateTime?>> assetTimestampFutures = {
|
|
||||||
for (final album in albums) album.id: _repository.getOldestAssetTimestamp(album.id),
|
|
||||||
};
|
|
||||||
|
|
||||||
// await all database queries
|
|
||||||
final entries = await Future.wait(
|
|
||||||
assetTimestampFutures.entries.map((entry) async => MapEntry(entry.key, await entry.value)),
|
|
||||||
);
|
|
||||||
final assetTimestamps = Map.fromEntries(entries);
|
|
||||||
|
|
||||||
final sorted = albums.sorted((a, b) {
|
|
||||||
final aDate = assetTimestamps[a.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
|
||||||
final bDate = assetTimestamps[b.id] ?? DateTime.fromMillisecondsSinceEpoch(0);
|
|
||||||
return aDate.compareTo(bDate);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sorted;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,12 +68,12 @@ class SyncStreamService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final semVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
|
final serverSemVer = SemVer(major: serverVersion.major, minor: serverVersion.minor, patch: serverVersion.patch_);
|
||||||
|
|
||||||
final value = Store.get(StoreKey.syncMigrationStatus, "[]");
|
final value = Store.get(StoreKey.syncMigrationStatus, "[]");
|
||||||
final migrations = (jsonDecode(value) as List).cast<String>();
|
final migrations = (jsonDecode(value) as List).cast<String>();
|
||||||
int previousLength = migrations.length;
|
int previousLength = migrations.length;
|
||||||
await _runPreSyncTasks(migrations, semVer);
|
await _runPreSyncTasks(migrations, serverSemVer);
|
||||||
|
|
||||||
if (migrations.length != previousLength) {
|
if (migrations.length != previousLength) {
|
||||||
_logger.info("Updated pre-sync migration status: $migrations");
|
_logger.info("Updated pre-sync migration status: $migrations");
|
||||||
@@ -82,10 +82,14 @@ class SyncStreamService {
|
|||||||
|
|
||||||
// Start the sync stream and handle events
|
// Start the sync stream and handle events
|
||||||
bool shouldReset = false;
|
bool shouldReset = false;
|
||||||
await _syncApiRepository.streamChanges(_handleEvents, onReset: () => shouldReset = true);
|
await _syncApiRepository.streamChanges(
|
||||||
|
_handleEvents,
|
||||||
|
serverVersion: serverSemVer,
|
||||||
|
onReset: () => shouldReset = true,
|
||||||
|
);
|
||||||
if (shouldReset) {
|
if (shouldReset) {
|
||||||
_logger.info("Resetting sync state as requested by server");
|
_logger.info("Resetting sync state as requested by server");
|
||||||
await _syncApiRepository.streamChanges(_handleEvents);
|
await _syncApiRepository.streamChanges(_handleEvents, serverVersion: serverSemVer);
|
||||||
}
|
}
|
||||||
|
|
||||||
previousLength = migrations.length;
|
previousLength = migrations.length;
|
||||||
@@ -282,6 +286,8 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deletePeopleV1(data.cast());
|
return _syncStreamRepository.deletePeopleV1(data.cast());
|
||||||
case SyncEntityType.assetFaceV1:
|
case SyncEntityType.assetFaceV1:
|
||||||
return _syncStreamRepository.updateAssetFacesV1(data.cast());
|
return _syncStreamRepository.updateAssetFacesV1(data.cast());
|
||||||
|
case SyncEntityType.assetFaceV2:
|
||||||
|
return _syncStreamRepository.updateAssetFacesV2(data.cast());
|
||||||
case SyncEntityType.assetFaceDeleteV1:
|
case SyncEntityType.assetFaceDeleteV1:
|
||||||
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
|
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -183,8 +183,8 @@ class TimelineService {
|
|||||||
return _buffer.slice(start, start + count);
|
return _buffer.slice(start, start + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-cache assets around the given index for asset viewer
|
// Preload assets around the given index for asset viewer
|
||||||
Future<void> preCacheAssets(int index) => _mutex.run(() => _loadAssets(index, math.min(5, _totalAssets - index)));
|
Future<void> preloadAssets(int index) => _mutex.run(() => _loadAssets(index, math.min(5, _totalAssets - index)));
|
||||||
|
|
||||||
BaseAsset getRandomAsset() => _buffer.elementAt(math.Random().nextInt(_buffer.length));
|
BaseAsset getRandomAsset() => _buffer.elementAt(math.Random().nextInt(_buffer.length));
|
||||||
|
|
||||||
|
|||||||
@@ -32,3 +32,125 @@ class FastClampingScrollPhysics extends ClampingScrollPhysics {
|
|||||||
damping: 80,
|
damping: 80,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SnapScrollPhysics extends ScrollPhysics {
|
||||||
|
static const _minFlingVelocity = 700.0;
|
||||||
|
static const minSnapDistance = 30.0;
|
||||||
|
|
||||||
|
static final _spring = SpringDescription.withDampingRatio(mass: .5, stiffness: 300);
|
||||||
|
|
||||||
|
const SnapScrollPhysics({super.parent});
|
||||||
|
|
||||||
|
@override
|
||||||
|
SnapScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
||||||
|
return SnapScrollPhysics(parent: buildParent(ancestor));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
|
||||||
|
assert(
|
||||||
|
position is SnapScrollPosition,
|
||||||
|
'SnapScrollPhysics can only be used with Scrollables that use a '
|
||||||
|
'controller whose createScrollPosition returns a SnapScrollPosition',
|
||||||
|
);
|
||||||
|
|
||||||
|
final snapOffset = (position as SnapScrollPosition).snapOffset;
|
||||||
|
if (snapOffset <= 0) {
|
||||||
|
return super.createBallisticSimulation(position, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position.pixels >= snapOffset) {
|
||||||
|
final simulation = super.createBallisticSimulation(position, velocity);
|
||||||
|
if (simulation == null || simulation.x(double.infinity) >= snapOffset) {
|
||||||
|
return simulation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScrollSpringSimulation(
|
||||||
|
_spring,
|
||||||
|
position.pixels,
|
||||||
|
target(position, velocity, snapOffset),
|
||||||
|
velocity,
|
||||||
|
tolerance: toleranceFor(position),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double target(ScrollMetrics position, double velocity, double snapOffset) {
|
||||||
|
if (velocity > _minFlingVelocity) return snapOffset;
|
||||||
|
if (velocity < -_minFlingVelocity) return position.pixels < snapOffset ? 0.0 : snapOffset;
|
||||||
|
return position.pixels < minSnapDistance ? 0.0 : snapOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SnapScrollPosition extends ScrollPositionWithSingleContext {
|
||||||
|
double snapOffset;
|
||||||
|
|
||||||
|
SnapScrollPosition({this.snapOffset = 0.0, required super.physics, required super.context, super.oldPosition});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProxyScrollController extends ScrollController {
|
||||||
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
ProxyScrollController({required this.scrollController});
|
||||||
|
|
||||||
|
SnapScrollPosition get snapPosition => position as SnapScrollPosition;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ScrollPosition createScrollPosition(ScrollPhysics physics, ScrollContext context, ScrollPosition? oldPosition) {
|
||||||
|
return ProxyScrollPosition(
|
||||||
|
scrollController: scrollController,
|
||||||
|
physics: physics,
|
||||||
|
context: context,
|
||||||
|
oldPosition: oldPosition,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProxyScrollPosition extends SnapScrollPosition {
|
||||||
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
ProxyScrollPosition({
|
||||||
|
required this.scrollController,
|
||||||
|
required super.physics,
|
||||||
|
required super.context,
|
||||||
|
super.oldPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
double setPixels(double newPixels) {
|
||||||
|
final overscroll = super.setPixels(newPixels);
|
||||||
|
if (scrollController.hasClients && scrollController.position.pixels != pixels) {
|
||||||
|
scrollController.position.forcePixels(pixels);
|
||||||
|
}
|
||||||
|
return overscroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forcePixels(double value) {
|
||||||
|
super.forcePixels(value);
|
||||||
|
if (scrollController.hasClients && scrollController.position.pixels != pixels) {
|
||||||
|
scrollController.position.forcePixels(pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get maxScrollExtent => scrollController.hasClients && scrollController.position.hasContentDimensions
|
||||||
|
? scrollController.position.maxScrollExtent
|
||||||
|
: super.maxScrollExtent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get minScrollExtent => scrollController.hasClients && scrollController.position.hasContentDimensions
|
||||||
|
? scrollController.position.minScrollExtent
|
||||||
|
: super.minScrollExtent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get viewportDimension => scrollController.hasClients && scrollController.position.hasViewportDimension
|
||||||
|
? scrollController.position.viewportDimension
|
||||||
|
: super.viewportDimension;
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user