Compare commits

..

1 Commits

Author SHA1 Message Date
Daniel Dietzler 75d1d21cc6 fix: asset update race condition 2025-03-16 21:50:39 +01:00
327 changed files with 10275 additions and 10861 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:2ef23730ec68d8511ec8e6e0b82550ca728b256805d81f60ed890f3bfb21cfb9 ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:9791f4aa527774bc370c6bd2f6705ce5a686f1e6f204badd8dfaacce28c631ae
FROM ${BASEIMAGE} FROM ${BASEIMAGE}
# Flutter SDK # Flutter SDK
-1
View File
@@ -1 +0,0 @@
22.14.0
@@ -1,5 +1,5 @@
title: '[Feature] feature-name-goes-here' title: "[Feature] feature-name-goes-here"
labels: ['feature'] labels: ["feature"]
body: body:
- type: markdown - type: markdown
@@ -13,7 +13,7 @@ body:
attributes: attributes:
label: I have searched the existing feature requests, both open and closed, to make sure this is not a duplicate request. label: I have searched the existing feature requests, both open and closed, to make sure this is not a duplicate request.
options: options:
- label: 'Yes' - label: "Yes"
required: true required: true
- type: textarea - type: textarea
+4 -5
View File
@@ -5,7 +5,7 @@ body:
attributes: attributes:
label: I have searched the existing issues, both open and closed, to make sure this is not a duplicate report. label: I have searched the existing issues, both open and closed, to make sure this is not a duplicate report.
options: options:
- label: 'Yes' - label: "Yes"
required: true required: true
- type: markdown - type: markdown
@@ -84,7 +84,7 @@ body:
id: repro id: repro
attributes: attributes:
label: Reproduction steps label: Reproduction steps
description: 'How do you trigger this bug? Please walk us through it step by step.' description: "How do you trigger this bug? Please walk us through it step by step."
value: | value: |
1. 1.
2. 2.
@@ -97,13 +97,12 @@ body:
id: logs id: logs
attributes: attributes:
label: Relevant log output label: Relevant log output
description: description: Please copy and paste any relevant logs below. (code formatting is
Please copy and paste any relevant logs below. (code formatting is
enabled, no need for backticks) enabled, no need for backticks)
render: shell render: shell
validations: validations:
required: false required: false
- type: textarea - type: textarea
attributes: attributes:
label: Additional information label: Additional information
-28
View File
@@ -1,28 +0,0 @@
{
"name": ".github",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"prettier": "^3.5.3"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"scripts": {
"format": "prettier --check .",
"format:fix": "prettier --write ."
},
"devDependencies": {
"prettier": "^3.5.3"
}
}
+33 -33
View File
@@ -1,33 +1,33 @@
changelog: changelog:
categories: categories:
- title: 🚨 Breaking Changes - title: 🚨 Breaking Changes
labels: labels:
- changelog:breaking-change - changelog:breaking-change
- title: 🫥 Deprecated Changes - title: 🫥 Deprecated Changes
labels: labels:
- changelog:deprecated - changelog:deprecated
- title: 🔒 Security - title: 🔒 Security
labels: labels:
- changelog:security - changelog:security
- title: 🚀 Features - title: 🚀 Features
labels: labels:
- changelog:feature - changelog:feature
- title: 🌟 Enhancements - title: 🌟 Enhancements
labels: labels:
- changelog:enhancement - changelog:enhancement
- title: 🐛 Bug fixes - title: 🐛 Bug fixes
labels: labels:
- changelog:bugfix - changelog:bugfix
- title: 📚 Documentation - title: 📚 Documentation
labels: labels:
- changelog:documentation - changelog:documentation
- title: 🌐 Translations - title: 🌐 Translations
labels: labels:
- changelog:translation - changelog:translation
+6 -6
View File
@@ -22,9 +22,9 @@ jobs:
should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- id: found_paths - id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 uses: dorny/paths-filter@v3
with: with:
filters: | filters: |
mobile: mobile:
@@ -51,18 +51,18 @@ jobs:
ref="${input_ref:-$github_ref}" ref="${input_ref:-$github_ref}"
echo "ref=$ref" >> $GITHUB_OUTPUT echo "ref=$ref" >> $GITHUB_OUTPUT
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@v4
with: with:
ref: ${{ steps.get-ref.outputs.ref }} ref: ${{ steps.get-ref.outputs.ref }}
- uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 - uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '17' java-version: '17'
cache: 'gradle' cache: 'gradle'
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 uses: subosito/flutter-action@v2
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -89,7 +89,7 @@ jobs:
flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64 flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64
- name: Publish Android Artifact - name: Publish Android Artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: release-apk-signed name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/*.apk path: mobile/build/app/outputs/flutter-apk/*.apk
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Cleanup - name: Cleanup
run: | run: |
+9 -8
View File
@@ -6,6 +6,7 @@ on:
- 'cli/**' - 'cli/**'
- '.github/workflows/cli.yml' - '.github/workflows/cli.yml'
pull_request: pull_request:
branches: [main]
paths: paths:
- 'cli/**' - 'cli/**'
- '.github/workflows/cli.yml' - '.github/workflows/cli.yml'
@@ -28,9 +29,9 @@ jobs:
working-directory: ./cli working-directory: ./cli
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@v4
# Setup .npmrc file to publish to npm # Setup .npmrc file to publish to npm
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 - uses: actions/setup-node@v4
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
@@ -52,16 +53,16 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 uses: docker/setup-qemu-action@v3.6.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@v3.10.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
registry: ghcr.io registry: ghcr.io
@@ -76,7 +77,7 @@ jobs:
- name: Generate docker image tags - name: Generate docker image tags
id: metadata id: metadata
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 uses: docker/metadata-action@v5
with: with:
flavor: | flavor: |
latest=false latest=false
@@ -87,7 +88,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@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 uses: docker/build-push-action@v6.15.0
with: with:
file: cli/Dockerfile file: cli/Dockerfile
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
+32 -31
View File
@@ -9,14 +9,14 @@
# the `language` matrix defined below to confirm you have the correct set of # the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages. # supported CodeQL languages.
# #
name: 'CodeQL' name: "CodeQL"
on: on:
push: push:
branches: ['main'] branches: [ "main" ]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: ['main'] branches: [ "main" ]
schedule: schedule:
- cron: '20 13 * * 1' - cron: '20 13 * * 1'
@@ -36,42 +36,43 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: ['javascript', 'python'] language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@5f8171a638ada777af81d42b55959a643bb29017 # v3 uses: github/codeql-action/init@v3
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.
# By default, queries listed here will override any specified in a config file. # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality # queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@5f8171a638ada777af81d42b55959a643bb29017 # v3
# ️ Command-line programs to run using the OS shell. # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# If the Autobuild fails above, remove it and uncomment the following three lines. # ️ Command-line programs to run using the OS shell.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# - run: | # If the Autobuild fails above, remove it and uncomment the following three lines.
# echo "Run, Build Application using script" # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis # - run: |
uses: github/codeql-action/analyze@5f8171a638ada777af81d42b55959a643bb29017 # v3 # echo "Run, Build Application using script"
with: # ./location_of_script_within_repo/buildscript.sh
category: '/language:${{matrix.language}}'
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
+42 -56
View File
@@ -23,9 +23,9 @@ jobs:
should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- id: found_paths - id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 uses: dorny/paths-filter@v3
with: with:
filters: | filters: |
server: server:
@@ -49,23 +49,23 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] suffix: ["", "-cuda", "-openvino", "-armnn"]
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Re-tag image - name: Re-tag image
run: | run: |
REGISTRY_NAME="ghcr.io" REGISTRY_NAME="ghcr.io"
REPOSITORY=${{ github.repository_owner }}/immich-machine-learning REPOSITORY=${{ github.repository_owner }}/immich-machine-learning
TAG_OLD=main${{ matrix.suffix }} TAG_OLD=main${{ matrix.suffix }}
TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} TAG_PR=${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }}
TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }} TAG_COMMIT=commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }}
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_PR $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD docker buildx imagetools create -t $REGISTRY_NAME/$REPOSITORY:$TAG_COMMIT $REGISTRY_NAME/$REPOSITORY:$TAG_OLD
retag_server: retag_server:
name: Re-Tag Server name: Re-Tag Server
@@ -74,10 +74,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
suffix: [''] suffix: [""]
steps: steps:
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -120,11 +120,6 @@ jobs:
device: cuda device: cuda
suffix: -cuda suffix: -cuda
- platform: linux/amd64
runner: mich
device: rocm
suffix: -rocm
- platform: linux/amd64 - platform: linux/amd64
runner: ubuntu-latest runner: ubuntu-latest
device: openvino device: openvino
@@ -135,11 +130,6 @@ jobs:
device: armnn device: armnn
suffix: -armnn suffix: -armnn
- platform: linux/arm64
runner: ubuntu-24.04-arm
device: rknn
suffix: -rknn
steps: steps:
- name: Prepare - name: Prepare
run: | run: |
@@ -147,13 +137,13 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 uses: docker/setup-buildx-action@v3.10.0
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
registry: ghcr.io registry: ghcr.io
@@ -180,7 +170,7 @@ jobs:
- name: Build and push image - name: Build and push image
id: build id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 uses: docker/build-push-action@v6.15.0
with: with:
context: ${{ env.context }} context: ${{ env.context }}
file: ${{ env.file }} file: ${{ env.file }}
@@ -205,7 +195,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: ml-digests-${{ matrix.device }}-${{ env.PLATFORM_PAIR }} name: ml-digests-${{ matrix.device }}-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -225,19 +215,15 @@ jobs:
- device: cpu - device: cpu
- device: cuda - device: cuda
suffix: -cuda suffix: -cuda
- device: rocm
suffix: -rocm
- device: openvino - device: openvino
suffix: -openvino suffix: -openvino
- device: armnn - device: armnn
suffix: -armnn suffix: -armnn
- device: rknn
suffix: -rknn
needs: needs:
- build_and_push_ml - build_and_push_ml
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 uses: actions/download-artifact@v4
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: ml-digests-${{ matrix.device }}-* pattern: ml-digests-${{ matrix.device }}-*
@@ -245,26 +231,26 @@ jobs:
- name: Login to Docker Hub - name: Login to Docker Hub
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 uses: docker/setup-buildx-action@v3
- name: Generate docker image tags - name: Generate docker image tags
id: meta id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 uses: docker/metadata-action@v5
env: env:
DOCKER_METADATA_PR_HEAD_SHA: 'true' DOCKER_METADATA_PR_HEAD_SHA: "true"
with: with:
flavor: | flavor: |
# Disable latest tag # Disable latest tag
@@ -315,13 +301,13 @@ jobs:
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
if: ${{ !github.event.pull_request.head.repo.fork }} if: ${{ !github.event.pull_request.head.repo.fork }}
with: with:
registry: ghcr.io registry: ghcr.io
@@ -348,7 +334,7 @@ jobs:
- name: Build and push image - name: Build and push image
id: build id: build
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 uses: docker/build-push-action@v6.15.0
with: with:
context: ${{ env.context }} context: ${{ env.context }}
file: ${{ env.file }} file: ${{ env.file }}
@@ -373,7 +359,7 @@ jobs:
touch "${{ runner.temp }}/digests/${digest#sha256:}" touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: server-digests-${{ env.PLATFORM_PAIR }} name: server-digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
@@ -391,7 +377,7 @@ jobs:
- build_and_push_server - build_and_push_server
steps: steps:
- name: Download digests - name: Download digests
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 uses: actions/download-artifact@v4
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: server-digests-* pattern: server-digests-*
@@ -399,26 +385,26 @@ jobs:
- name: Login to Docker Hub - name: Login to Docker Hub
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 uses: docker/setup-buildx-action@v3
- name: Generate docker image tags - name: Generate docker image tags
id: meta id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 uses: docker/metadata-action@v5
env: env:
DOCKER_METADATA_PR_HEAD_SHA: 'true' DOCKER_METADATA_PR_HEAD_SHA: "true"
with: with:
flavor: | flavor: |
# Disable latest tag # Disable latest tag
+6 -5
View File
@@ -3,6 +3,7 @@ on:
push: push:
branches: [main] branches: [main]
pull_request: pull_request:
branches: [main]
release: release:
types: [published] types: [published]
@@ -17,9 +18,9 @@ jobs:
should_run: ${{ steps.found_paths.outputs.docs == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run: ${{ steps.found_paths.outputs.docs == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- id: found_paths - id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 uses: dorny/paths-filter@v3
with: with:
filters: | filters: |
docs: docs:
@@ -41,10 +42,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './docs/.nvmrc' node-version-file: './docs/.nvmrc'
@@ -58,7 +59,7 @@ jobs:
run: npm run build run: npm run build
- name: Upload build output - name: Upload build output
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: docs-build-output name: docs-build-output
path: docs/build/ path: docs/build/
+21 -21
View File
@@ -1,7 +1,7 @@
name: Docs deploy name: Docs deploy
on: on:
workflow_run: workflow_run:
workflows: ['Docs build'] workflows: ["Docs build"]
types: types:
- completed - completed
@@ -17,7 +17,7 @@ jobs:
run: echo 'The triggering workflow did not succeed' && exit 1 run: echo 'The triggering workflow did not succeed' && exit 1
- name: Get artifact - name: Get artifact
id: get-artifact id: get-artifact
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 uses: actions/github-script@v7
with: with:
script: | script: |
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -35,7 +35,7 @@ jobs:
return { found: true, id: matchArtifact.id }; return { found: true, id: matchArtifact.id };
- name: Determine deploy parameters - name: Determine deploy parameters
id: parameters id: parameters
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 uses: actions/github-script@v7
with: with:
script: | script: |
const eventType = context.payload.workflow_run.event; const eventType = context.payload.workflow_run.event;
@@ -98,11 +98,11 @@ jobs:
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }} if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Load parameters - name: Load parameters
id: parameters id: parameters
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 uses: actions/github-script@v7
with: with:
script: | script: |
const json = `${{ needs.checks.outputs.parameters }}`; const json = `${{ needs.checks.outputs.parameters }}`;
@@ -115,7 +115,7 @@ jobs:
echo "Starting docs deployment for ${{ steps.parameters.outputs.event }} ${{ steps.parameters.outputs.name }}" echo "Starting docs deployment for ${{ steps.parameters.outputs.event }} ${{ steps.parameters.outputs.name }}"
- name: Download artifact - name: Download artifact
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 uses: actions/github-script@v7
with: with:
script: | script: |
let artifact = ${{ needs.checks.outputs.artifact }}; let artifact = ${{ needs.checks.outputs.artifact }};
@@ -138,12 +138,12 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 uses: gruntwork-io/terragrunt-action@v2
with: with:
tg_version: '0.58.12' tg_version: "0.58.12"
tofu_version: '1.7.1' tofu_version: "1.7.1"
tg_dir: 'deployment/modules/cloudflare/docs' tg_dir: "deployment/modules/cloudflare/docs"
tg_command: 'apply' tg_command: "apply"
- name: Deploy Docs Subdomain Output - name: Deploy Docs Subdomain Output
id: docs-output id: docs-output
@@ -153,12 +153,12 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 uses: gruntwork-io/terragrunt-action@v2
with: with:
tg_version: '0.58.12' tg_version: "0.58.12"
tofu_version: '1.7.1' tofu_version: "1.7.1"
tg_dir: 'deployment/modules/cloudflare/docs' tg_dir: "deployment/modules/cloudflare/docs"
tg_command: 'output -json' tg_command: "output -json"
- name: Output Cleaning - name: Output Cleaning
id: clean id: clean
@@ -167,13 +167,13 @@ jobs:
echo "output=$TG_OUT" >> $GITHUB_OUTPUT echo "output=$TG_OUT" >> $GITHUB_OUTPUT
- name: Publish to Cloudflare Pages - name: Publish to Cloudflare Pages
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1 uses: cloudflare/pages-action@v1
with: with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }} apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ fromJson(steps.clean.outputs.output).pages_project_name.value }} projectName: ${{ fromJson(steps.clean.outputs.output).pages_project_name.value }}
workingDirectory: 'docs' workingDirectory: "docs"
directory: 'build' directory: "build"
branch: ${{ steps.parameters.outputs.name }} branch: ${{ steps.parameters.outputs.name }}
wranglerVersion: '3' wranglerVersion: '3'
@@ -184,7 +184,7 @@ jobs:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 uses: gruntwork-io/terragrunt-action@v2
with: with:
tg_version: '0.58.12' tg_version: '0.58.12'
tofu_version: '1.7.1' tofu_version: '1.7.1'
@@ -192,7 +192,7 @@ jobs:
tg_command: 'apply' tg_command: 'apply'
- name: Comment - name: Comment
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3 uses: actions-cool/maintain-one-comment@v3
if: ${{ steps.parameters.outputs.event == 'pr' }} if: ${{ steps.parameters.outputs.event == 'pr' }}
with: with:
number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }} number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }}
+9 -9
View File
@@ -9,24 +9,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Destroy Docs Subdomain - name: Destroy Docs Subdomain
env: env:
TF_VAR_prefix_name: 'pr-${{ github.event.number }}' TF_VAR_prefix_name: "pr-${{ github.event.number }}"
TF_VAR_prefix_event_type: 'pr' TF_VAR_prefix_event_type: "pr"
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }}
uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 uses: gruntwork-io/terragrunt-action@v2
with: with:
tg_version: '0.58.12' tg_version: "0.58.12"
tofu_version: '1.7.1' tofu_version: "1.7.1"
tg_dir: 'deployment/modules/cloudflare/docs' tg_dir: "deployment/modules/cloudflare/docs"
tg_command: 'destroy -refresh=false' tg_command: "destroy -refresh=false"
- name: Comment - name: Comment
uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3 uses: actions-cool/maintain-one-comment@v3
with: with:
number: ${{ github.event.number }} number: ${{ github.event.number }}
delete: true delete: true
+6 -5
View File
@@ -13,19 +13,19 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@af35edadc00be37caa72ed9f3e6d5f7801bfdf09 # v1 uses: actions/create-github-app-token@v1
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
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 }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
@@ -33,13 +33,13 @@ jobs:
run: make install-all && make format-all run: make install-all && make format-all
- name: Commit and push - name: Commit and push
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9 uses: EndBug/add-and-commit@v9
with: with:
default_author: github_actions default_author: github_actions
message: 'chore: fix formatting' message: 'chore: fix formatting'
- name: Remove label - name: Remove label
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 uses: actions/github-script@v7
if: always() if: always()
with: with:
script: | script: |
@@ -49,3 +49,4 @@ jobs:
repo: context.repo.repo, repo: context.repo.repo,
name: 'fix:formatting' name: 'fix:formatting'
}) })
+3 -3
View File
@@ -12,11 +12,11 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Require PR to have a changelog label - name: Require PR to have a changelog label
uses: mheap/github-action-required-labels@388fd6af37b34cdfe5a23b37060e763217e58b03 # v5 uses: mheap/github-action-required-labels@v5
with: with:
mode: exactly mode: exactly
count: 1 count: 1
use_regex: true use_regex: true
labels: 'changelog:.*' labels: "changelog:.*"
add_comment: true add_comment: true
message: 'Label error. Requires {{errorString}} {{count}} of: {{ provided }}. Found: {{ applied }}. A maintainer will add the required label.' message: "Label error. Requires {{errorString}} {{count}} of: {{ provided }}. Found: {{ applied }}. A maintainer will add the required label."
+3 -3
View File
@@ -1,6 +1,6 @@
name: 'Pull Request Labeler' name: "Pull Request Labeler"
on: on:
- pull_request_target - pull_request_target
jobs: jobs:
labeler: labeler:
@@ -9,4 +9,4 @@ jobs:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 - uses: actions/labeler@v5
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: PR Conventional Commit Validation - name: PR Conventional Commit Validation
uses: ytanikin/PRConventionalCommits@1.3.0 uses: ytanikin/PRConventionalCommits@1.3.0
with: with:
task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]' task_types: '["feat","fix","docs","test","ci","refactor","perf","chore","revert"]'
add_label: 'false' add_label: 'false'
+8 -8
View File
@@ -31,25 +31,25 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@af35edadc00be37caa72ed9f3e6d5f7801bfdf09 # v1 uses: actions/create-github-app-token@v1
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5 uses: astral-sh/setup-uv@v5
- name: Bump version - name: Bump version
run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}" run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}"
- name: Commit and tag - name: Commit and tag
id: push-tag id: push-tag
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9 uses: EndBug/add-and-commit@v9
with: with:
default_author: github_actions default_author: github_actions
message: 'chore: version ${{ env.IMMICH_VERSION }}' message: 'chore: version ${{ env.IMMICH_VERSION }}'
@@ -70,23 +70,23 @@ jobs:
steps: steps:
- name: Generate a token - name: Generate a token
id: generate-token id: generate-token
uses: actions/create-github-app-token@af35edadc00be37caa72ed9f3e6d5f7801bfdf09 # v1 uses: actions/create-github-app-token@v1
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
with: with:
token: ${{ steps.generate-token.outputs.token }} token: ${{ steps.generate-token.outputs.token }}
- name: Download APK - name: Download APK
uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 uses: actions/download-artifact@v4
with: with:
name: release-apk-signed name: release-apk-signed
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2 uses: softprops/action-gh-release@v2
with: with:
draft: true draft: true
tag_name: ${{ env.IMMICH_VERSION }} tag_name: ${{ env.IMMICH_VERSION }}
+4 -4
View File
@@ -11,10 +11,10 @@ jobs:
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 - uses: mshick/add-pr-comment@v2
with: with:
message-id: 'preview-status' message-id: "preview-status"
message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/' message: "Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/"
remove-label: remove-label:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -22,7 +22,7 @@ jobs:
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - uses: actions/github-script@v7
with: with:
script: | script: |
github.rest.issues.removeLabel({ github.rest.issues.removeLabel({
+2 -2
View File
@@ -15,9 +15,9 @@ jobs:
run: run:
working-directory: ./open-api/typescript-sdk working-directory: ./open-api/typescript-sdk
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@v4
# Setup .npmrc file to publish to npm # Setup .npmrc file to publish to npm
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 - uses: actions/setup-node@v4
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'
+5 -5
View File
@@ -16,9 +16,9 @@ jobs:
should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- id: found_paths - id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 uses: dorny/paths-filter@v3
with: with:
filters: | filters: |
mobile: mobile:
@@ -38,10 +38,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 uses: subosito/flutter-action@v2
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -55,7 +55,7 @@ jobs:
working-directory: ./mobile working-directory: ./mobile
- name: Find file changes - name: Find file changes
uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 uses: tj-actions/verify-changed-files@v20
id: verify-changed-files id: verify-changed-files
with: with:
files: | files: |
+31 -59
View File
@@ -21,12 +21,11 @@ jobs:
should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run_e2e_web: ${{ steps.found_paths.outputs.e2e == 'true' || steps.found_paths.outputs.web == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run_e2e_web: ${{ steps.found_paths.outputs.e2e == 'true' || steps.found_paths.outputs.web == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run_e2e_server_cli: ${{ steps.found_paths.outputs.e2e == 'true' || steps.found_paths.outputs.server == 'true' || steps.found_paths.outputs.cli == 'true' || steps.should_force.outputs.should_force == 'true' }} should_run_e2e_server_cli: ${{ steps.found_paths.outputs.e2e == 'true' || steps.found_paths.outputs.server == 'true' || steps.found_paths.outputs.cli == 'true' || steps.should_force.outputs.should_force == 'true' }}
should_run_.github: ${{ steps.found_paths.outputs['.github'] == 'true' || steps.should_force.outputs.should_force == 'true' }} # redundant to have should_force but if someone changes the trigger then this won't have to be changed
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- id: found_paths - id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 uses: dorny/paths-filter@v3
with: with:
filters: | filters: |
web: web:
@@ -46,8 +45,6 @@ jobs:
- 'machine-learning/**' - 'machine-learning/**'
workflow: workflow:
- '.github/workflows/test.yml' - '.github/workflows/test.yml'
.github:
- '.github/**'
- name: Check if we should force jobs to run - name: Check if we should force jobs to run
id: should_force id: should_force
@@ -64,10 +61,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
@@ -101,10 +98,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
@@ -142,10 +139,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './cli/.nvmrc' node-version-file: './cli/.nvmrc'
@@ -176,10 +173,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './web/.nvmrc' node-version-file: './web/.nvmrc'
@@ -221,10 +218,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
@@ -260,10 +257,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
@@ -285,12 +282,12 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
@@ -327,12 +324,12 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './e2e/.nvmrc' node-version-file: './e2e/.nvmrc'
@@ -363,9 +360,9 @@ jobs:
if: ${{ needs.pre-job.outputs.should_run_mobile == 'true' }} if: ${{ needs.pre-job.outputs.should_run_mobile == 'true' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@v4
- name: Setup Flutter SDK - name: Setup Flutter SDK
uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 uses: subosito/flutter-action@v2
with: with:
channel: 'stable' channel: 'stable'
flutter-version-file: ./mobile/pubspec.yaml flutter-version-file: ./mobile/pubspec.yaml
@@ -382,10 +379,10 @@ jobs:
run: run:
working-directory: ./machine-learning working-directory: ./machine-learning
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@v4
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5 uses: astral-sh/setup-uv@v5
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 - uses: actions/setup-python@v5
# TODO: add caching when supported (https://github.com/actions/setup-python/pull/818) # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818)
# with: # with:
# python-version: 3.11 # python-version: 3.11
@@ -406,36 +403,11 @@ jobs:
run: | run: |
uv run pytest app --cov=app --cov-report term-missing uv run pytest app --cov=app --cov-report term-missing
github-files-formatting:
name: .github Files Formatting
needs: pre-job
if: ${{ needs.pre-job.outputs['should_run_.github'] == 'true' }}
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./.github
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './.github/.nvmrc'
- name: Run npm install
run: npm ci
- name: Run formatter
run: npm run format
if: ${{ !cancelled() }}
shellcheck: shellcheck:
name: ShellCheck name: ShellCheck
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: actions/checkout@v4
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
with: with:
@@ -449,10 +421,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
@@ -466,7 +438,7 @@ jobs:
run: make open-api run: make open-api
- name: Find file changes - name: Find file changes
uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 uses: tj-actions/verify-changed-files@v20
id: verify-changed-files id: verify-changed-files
with: with:
files: | files: |
@@ -504,10 +476,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- name: Setup Node - name: Setup Node
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 uses: actions/setup-node@v4
with: with:
node-version-file: './server/.nvmrc' node-version-file: './server/.nvmrc'
@@ -528,7 +500,7 @@ jobs:
run: npm run typeorm:migrations:generate ./src/migrations/TestMigration run: npm run typeorm:migrations:generate ./src/migrations/TestMigration
- name: Find file changes - name: Find file changes
uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 uses: tj-actions/verify-changed-files@v20
id: verify-changed-files id: verify-changed-files
with: with:
files: | files: |
@@ -547,7 +519,7 @@ jobs:
DB_URL: postgres://postgres:postgres@localhost:5432/immich DB_URL: postgres://postgres:postgres@localhost:5432/immich
- name: Find file changes - name: Find file changes
uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 uses: tj-actions/verify-changed-files@v20
id: verify-changed-sql-files id: verify-changed-sql-files
with: with:
files: | files: |
+8 -8
View File
@@ -11,22 +11,22 @@ jobs:
should_run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}} should_run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@v4
- id: found_paths - id: found_paths
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 uses: dorny/paths-filter@v3
with: with:
filters: | filters: |
i18n: i18n:
- 'i18n/!(en)**\.json' - 'i18n/!(en)**\.json'
- name: Debug - name: Debug
run: | run: |
echo "Should run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}" echo "Should run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}}"
echo "Found i18n paths: ${{ steps.found_paths.outputs.i18n }}" echo "Found i18n paths: ${{ steps.found_paths.outputs.i18n }}"
echo "Head ref: ${{ github.head_ref }}" echo "Head ref: ${{ github.head_ref }}"
enforce-lock: enforce-lock:
name: Check Weblate Lock name: Check Weblate Lock
needs: [pre-job] needs: [ pre-job ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ needs.pre-job.outputs.should_run == 'true' }} if: ${{ needs.pre-job.outputs.should_run == 'true' }}
steps: steps:
@@ -36,7 +36,7 @@ jobs:
exit 1 exit 1
fi fi
- name: Find Pull Request - name: Find Pull Request
uses: juliangruber/find-pull-request-action@48b6133aa6c826f267ebd33aa2d29470f9d9e7d0 # v1 uses: juliangruber/find-pull-request-action@v1
id: find-pr id: find-pr
with: with:
branch: chore/translations branch: chore/translations
@@ -45,7 +45,7 @@ jobs:
run: exit 1 run: exit 1
success-check-lock: success-check-lock:
name: Weblate Lock Check Success name: Weblate Lock Check Success
needs: [enforce-lock] needs: [ enforce-lock ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: always() if: always()
steps: steps:
+5 -5
View File
@@ -39,7 +39,7 @@ attach-server:
renovate: renovate:
LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
MODULES = e2e server web cli sdk docs .github MODULES = e2e server web cli sdk docs
audit-%: audit-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix
@@ -77,14 +77,14 @@ test-medium:
test-medium-dev: test-medium-dev:
docker exec -it immich_server /bin/sh -c "npm run test:medium" docker exec -it immich_server /bin/sh -c "npm run test:medium"
build-all: $(foreach M,$(filter-out e2e .github,$(MODULES)),build-$M) ; build-all: $(foreach M,$(filter-out e2e,$(MODULES)),build-$M) ;
install-all: $(foreach M,$(MODULES),install-$M) ; install-all: $(foreach M,$(MODULES),install-$M) ;
check-all: $(foreach M,$(filter-out sdk cli docs .github,$(MODULES)),check-$M) ; check-all: $(foreach M,$(filter-out sdk cli docs,$(MODULES)),check-$M) ;
lint-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),lint-$M) ; lint-all: $(foreach M,$(filter-out sdk docs,$(MODULES)),lint-$M) ;
format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ; format-all: $(foreach M,$(filter-out sdk,$(MODULES)),format-$M) ;
audit-all: $(foreach M,$(MODULES),audit-$M) ; audit-all: $(foreach M,$(MODULES),audit-$M) ;
hygiene-all: lint-all format-all check-all sql audit-all; hygiene-all: lint-all format-all check-all sql audit-all;
test-all: $(foreach M,$(filter-out sdk docs .github,$(MODULES)),test-$M) ; test-all: $(foreach M,$(filter-out sdk docs,$(MODULES)),test-$M) ;
clean: clean:
find . -name "node_modules" -type d -prune -exec rm -rf '{}' + find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
+58 -69
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.54", "version": "2.2.53",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.54", "version": "2.2.53",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -27,7 +27,7 @@
"@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": "^22.13.10", "@types/node": "^22.13.9",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0", "@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
@@ -55,14 +55,14 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.0", "version": "1.129.0",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.10", "@types/node": "^22.13.9",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
}, },
@@ -809,16 +809,6 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/@eslint/config-helpers": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
"integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
@@ -894,9 +884,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.22.0", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz",
"integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1528,17 +1518,17 @@
"dev": true "dev": true
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz",
"integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/scope-manager": "8.26.0",
"@typescript-eslint/type-utils": "8.26.1", "@typescript-eslint/type-utils": "8.26.0",
"@typescript-eslint/utils": "8.26.1", "@typescript-eslint/utils": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -1558,16 +1548,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz",
"integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/scope-manager": "8.26.0",
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1583,14 +1573,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz",
"integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1" "@typescript-eslint/visitor-keys": "8.26.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1601,14 +1591,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz",
"integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.0",
"@typescript-eslint/utils": "8.26.1", "@typescript-eslint/utils": "8.26.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.0.1"
}, },
@@ -1625,9 +1615,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz",
"integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1639,14 +1629,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz",
"integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -1666,16 +1656,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz",
"integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/scope-manager": "8.26.0",
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/typescript-estree": "8.26.1" "@typescript-eslint/typescript-estree": "8.26.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1690,13 +1680,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz",
"integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@@ -2379,19 +2369,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.22.0", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz",
"integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.2", "@eslint/config-array": "^0.19.2",
"@eslint/config-helpers": "^0.1.0",
"@eslint/core": "^0.12.0", "@eslint/core": "^0.12.0",
"@eslint/eslintrc": "^3.3.0", "@eslint/eslintrc": "^3.3.0",
"@eslint/js": "9.22.0", "@eslint/js": "9.21.0",
"@eslint/plugin-kit": "^0.2.7", "@eslint/plugin-kit": "^0.2.7",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -2403,7 +2392,7 @@
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.3.0", "eslint-scope": "^8.2.0",
"eslint-visitor-keys": "^4.2.0", "eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0", "espree": "^10.3.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
@@ -2531,9 +2520,9 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.3.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.54", "version": "2.2.53",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
@@ -21,7 +21,7 @@
"@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": "^22.13.10", "@types/node": "^22.13.9",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0", "@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
+2 -2
View File
@@ -95,12 +95,12 @@ services:
image: immich-machine-learning-dev:latest image: immich-machine-learning-dev:latest
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
build: build:
context: ../machine-learning context: ../machine-learning
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
- DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
ports: ports:
- 3003:3003 - 3003:3003
volumes: volumes:
+16 -6
View File
@@ -38,12 +38,12 @@ services:
image: immich-machine-learning:latest image: immich-machine-learning:latest
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
build: build:
context: ../machine-learning context: ../machine-learning
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
- DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference
ports: ports:
- 3003:3003 - 3003:3003
volumes: volumes:
@@ -77,12 +77,22 @@ services:
- 5432:5432 - 5432:5432
healthcheck: healthcheck:
test: >- test: >-
pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1 pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
--command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
echo "checksum failure count is $$Chksum";
[ "$$Chksum" = '0' ] || exit 1
interval: 5m interval: 5m
start_interval: 30s start_interval: 30s
start_period: 5m start_period: 5m
command: >- command: >-
postgres -c shared_preload_libraries=vectors.so -c 'search_path="$$user", public, vectors' -c logging_collector=on -c max_wal_size=2GB -c shared_buffers=512MB -c wal_compression=on postgres
-c shared_preload_libraries=vectors.so
-c 'search_path="$$user", public, vectors'
-c logging_collector=on
-c max_wal_size=2GB
-c shared_buffers=512MB
-c wal_compression=on
restart: always restart: always
# set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics
@@ -90,7 +100,7 @@ services:
container_name: immich_prometheus container_name: immich_prometheus
ports: ports:
- 9090:9090 - 9090:9090
image: prom/prometheus@sha256:502ad90314c7485892ce696cb14a99fceab9fc27af29f4b427f41bd39701a199 image: prom/prometheus@sha256:6927e0919a144aa7616fd0137d4816816d42f6b816de3af269ab065250859a62
volumes: volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml - ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus - prometheus-data:/prometheus
@@ -99,7 +109,7 @@ services:
# add data source for http://immich-prometheus:9090 to get started # add data source for http://immich-prometheus:9090 to get started
immich-grafana: immich-grafana:
container_name: immich_grafana container_name: immich_grafana
command: [ './run.sh', '-disable-reporting' ] command: ['./run.sh', '-disable-reporting']
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:11.5.2-ubuntu@sha256:8b5858c447e06fd7a89006b562ba7bba7c4d5813600c7982374c41852adefaeb image: grafana/grafana:11.5.2-ubuntu@sha256:8b5858c447e06fd7a89006b562ba7bba7c4d5813600c7982374c41852adefaeb
+14 -4
View File
@@ -33,12 +33,12 @@ services:
immich-machine-learning: immich-machine-learning:
container_name: immich_machine_learning container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda # Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes: volumes:
- model-cache:/cache - model-cache:/cache
env_file: env_file:
@@ -67,12 +67,22 @@ services:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck: healthcheck:
test: >- test: >-
pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1 pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
--command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
echo "checksum failure count is $$Chksum";
[ "$$Chksum" = '0' ] || exit 1
interval: 5m interval: 5m
start_interval: 30s start_interval: 30s
start_period: 5m start_period: 5m
command: >- command: >-
postgres -c shared_preload_libraries=vectors.so -c 'search_path="$$user", public, vectors' -c logging_collector=on -c max_wal_size=2GB -c shared_buffers=512MB -c wal_compression=on postgres
-c shared_preload_libraries=vectors.so
-c 'search_path="$$user", public, vectors'
-c logging_collector=on
-c max_wal_size=2GB
-c shared_buffers=512MB
-c wal_compression=on
restart: always restart: always
volumes: volumes:
-14
View File
@@ -13,13 +13,6 @@ services:
volumes: volumes:
- /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver) - /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver)
- /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required) - /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required)
rknn:
security_opt:
- systempaths=unconfined
- apparmor=unconfined
devices:
- /dev/dri:/dev/dri
cpu: {} cpu: {}
@@ -33,13 +26,6 @@ services:
capabilities: capabilities:
- gpu - gpu
rocm:
group_add:
- video
devices:
- /dev/dri:/dev/dri
- /dev/kfd:/dev/kfd
openvino: openvino:
device_cgroup_rules: device_cgroup_rules:
- 'c 189:* rmw' - 'c 189:* rmw'
+1 -1
View File
@@ -262,7 +262,7 @@ No, this is not supported. Only models listed in the [Hugging Face][huggingface]
### I want to be able to search in other languages besides English. How can I do that? ### I want to be able to search in other languages besides English. How can I do that?
You can change to a multilingual CLIP model. See [here](/docs/features/searching#clip-models) for instructions. You can change to a multilingual CLIP model. See [here](/docs/features/searching#clip-model) for instructions.
### Does Immich support Facial Recognition for videos? ### Does Immich support Facial Recognition for videos?
@@ -11,7 +11,6 @@ The `immich-server` docker image comes preinstalled with an administrative CLI (
| `enable-oauth-login` | Enable OAuth login | | `enable-oauth-login` | Enable OAuth login |
| `disable-oauth-login` | Disable OAuth login | | `disable-oauth-login` | Disable OAuth login |
| `list-users` | List Immich users | | `list-users` | List Immich users |
| `version` | Print Immich version |
## How to run a command ## How to run a command
@@ -81,10 +80,3 @@ immich-admin list-users
} }
] ]
``` ```
Print Immich Version
```
immich-admin version
v1.129.0
```
+1 -1
View File
@@ -95,7 +95,7 @@ The `immich-server` container will need access to the gallery. Modify your docke
+ - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro + - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+ - /home/user/old-pics:/mnt/media/old-pics:ro + - /home/user/old-pics:/mnt/media/old-pics:ro
+ - /mnt/media/videos:/mnt/media/videos:ro + - /mnt/media/videos:/mnt/media/videos:ro
+ - /mnt/media/videos2:/mnt/media/videos2 # WARNING: Immich will be able to delete the files in this folder, as it does not end with :ro + - /mnt/media/videos2:/mnt/media/videos2 # the files in this folder can be deleted, as it does not end with :ro
+ - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system. + - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.
``` ```
+2 -31
View File
@@ -11,9 +11,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- ARM NN (Mali) - ARM NN (Mali)
- CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher) - CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher)
- ROCm (AMD GPUs)
- OpenVINO (Intel GPUs such as Iris Xe and Arc) - OpenVINO (Intel GPUs such as Iris Xe and Arc)
- RKNN (Rockchip)
## Limitations ## Limitations
@@ -21,7 +19,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- Only Linux and Windows (through WSL2) servers are supported. - Only Linux and Windows (through WSL2) servers are supported.
- ARM NN is only supported on devices with Mali GPUs. Other Arm devices are not supported. - ARM NN is only supported on devices with Mali GPUs. Other Arm devices are not supported.
- Some models may not be compatible with certain backends. CUDA is the most reliable. - Some models may not be compatible with certain backends. CUDA is the most reliable.
- Search latency isn't improved by ARM NN due to model compatibility issues preventing its use. However, smart search jobs do make use of ARM NN.
## Prerequisites ## Prerequisites
@@ -36,7 +33,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere - The `hwaccel.ml.yml` file assumes the path to it is `/usr/lib/libmali.so`, so update accordingly if it is elsewhere
- The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file - The `hwaccel.ml.yml` file assumes an additional file `/lib/firmware/mali_csffw.bin`, so update accordingly if your device's driver does not require this file
- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for ARM NN specific settings - Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for ARM NN specific settings
- In particular, the `MACHINE_LEARNING_ANN_FP16_TURBO` can significantly improve performance at the cost of very slightly lower accuracy
#### CUDA #### CUDA
@@ -45,38 +41,22 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- The installed driver must be >= 535 (it must support CUDA 12.2). - The installed driver must be >= 535 (it must support CUDA 12.2).
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed. - On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
#### ROCm
- The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=<a supported version, e.g. 10.3.0>`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`.
- The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached.
- This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/docs/install/environment-variables) setting).
#### OpenVINO #### OpenVINO
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM. - Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM.
- Ensure the server's kernel version is new enough to use the device for hardware accceleration. - Ensure the server's kernel version is new enough to use the device for hardware accceleration.
- Expect higher RAM usage when using OpenVINO compared to CPU processing. - Expect higher RAM usage when using OpenVINO compared to CPU processing.
#### RKNN
- You must have a supported Rockchip SoC: only RK3566, RK3568, RK3576 and RK3588 are supported at this moment.
- Make sure you have the appropriate linux kernel driver installed
- This is usually pre-installed on the device vendor's Linux images
- RKNPU driver V0.9.8 or later must be available in the host server
- You may confirm this by running `cat /sys/kernel/debug/rknpu/version` to check the version
- Optional: Configure your `.env` file, see [environment variables](/docs/install/environment-variables) for RKNN specific settings
- In particular, setting `MACHINE_LEARNING_RKNN_THREADS` to 2 or 3 can _dramatically_ improve performance for RK3576 and RK3588 compared to the default of 1, at the expense of multiplying the amount of RAM each model uses by that amount.
## Setup ## Setup
1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`. 1. If you do not already have it, download the latest [`hwaccel.ml.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend. 2. In the `docker-compose.yml` under `immich-machine-learning`, uncomment the `extends` section and change `cpu` to the appropriate backend.
3. Still in `immich-machine-learning`, add one of -[armnn, cuda, rocm, openvino] to the `image` section's tag at the end of the line. 3. Still in `immich-machine-learning`, add one of -[armnn, cuda, openvino] to the `image` section's tag at the end of the line.
4. Redeploy the `immich-machine-learning` container with these updated settings. 4. Redeploy the `immich-machine-learning` container with these updated settings.
### Confirming Device Usage ### Confirming Device Usage
You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel, `intel_gpu_top` for Intel, and `radeontop` for AMD. You can confirm the device is being recognized and used by checking its utilization. There are many tools to display this, such as `nvtop` for NVIDIA or Intel and `intel_gpu_top` for Intel.
You can also check the logs of the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, or when you search with text in Immich, you should either see a log for `Available ORT providers` containing the relevant provider (e.g. `CUDAExecutionProvider` in the case of CUDA), or a `Loaded ANN model` log entry without errors in the case of ARM NN. You can also check the logs of the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, or when you search with text in Immich, you should either see a log for `Available ORT providers` containing the relevant provider (e.g. `CUDAExecutionProvider` in the case of CUDA), or a `Loaded ANN model` log entry without errors in the case of ARM NN.
@@ -147,12 +127,3 @@ Note that you should increase job concurrencies to increase overall utilization
- If you encounter an error when a model is running, try a different model to see if the issue is model-specific. - If you encounter an error when a model is running, try a different model to see if the issue is model-specific.
- You may want to increase concurrency past the default for higher utilization. However, keep in mind that this will also increase VRAM consumption. - You may want to increase concurrency past the default for higher utilization. However, keep in mind that this will also increase VRAM consumption.
- Larger models benefit more from hardware acceleration, if you have the VRAM for them. - Larger models benefit more from hardware acceleration, if you have the VRAM for them.
- Compared to ARM NN, RKNPU has:
- Wider model support (including for search, which ARM NN does not accelerate)
- Less heat generation
- Very slightly lower accuracy (RKNPU always uses FP16, while ARM NN by default uses higher precision FP32 unless `MACHINE_LEARNING_ANN_FP16_TURBO` is enabled)
- Varying speed (tested on RK3588):
- If `MACHINE_LEARNING_RKNN_THREADS` is at the default of 1, RKNPU will have substantially lower throughput for ML jobs than ARM NN in most cases, but similar latency (such as when searching)
- If `MACHINE_LEARNING_RKNN_THREADS` is set to 3, it will be somewhat faster than ARM NN at FP32, but somewhat slower than ARM NN if `MACHINE_LEARNING_ANN_FP16_TURBO` is enabled
- When other tasks also use the GPU (like transcoding), RKNPU has a significant advantage over ARM NN as it uses the otherwise idle NPU instead of competing for GPU usage
- Lower RAM usage if `MACHINE_LEARNING_RKNN_THREADS` is at the default of 1, but significantly higher if greater than 1 (which is necessary for it to fully utilize the NPU and hence be comparable in speed to ARM NN)
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -23,12 +23,12 @@ name: immich_remote_ml
services: services:
immich-machine-learning: immich-machine-learning:
container_name: immich_machine_learning container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, rocm, openvino] to the image tag. # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda # Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # extends:
# file: hwaccel.ml.yml # file: hwaccel.ml.yml
# service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable # service: # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes: volumes:
- model-cache:/cache - model-cache:/cache
restart: always restart: always
@@ -170,8 +170,6 @@ Redis (Sentinel) URL example JSON before encoding:
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | | `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
| `MACHINE_LEARNING_PING_TIMEOUT` | How long (ms) to wait for a PING response when checking if an ML server is available | `2000` | server | | `MACHINE_LEARNING_PING_TIMEOUT` | How long (ms) to wait for a PING response when checking if an ML server is available | `2000` | server |
| `MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME` | How long to ignore ML servers that are offline before trying again | `30000` | server | | `MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME` | How long to ignore ML servers that are offline before trying again | `30000` | server |
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. \*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
+9 -9
View File
@@ -5308,9 +5308,9 @@
} }
}, },
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.21", "version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -5327,11 +5327,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"browserslist": "^4.24.4", "browserslist": "^4.23.3",
"caniuse-lite": "^1.0.30001702", "caniuse-lite": "^1.0.30001646",
"fraction.js": "^4.3.7", "fraction.js": "^4.3.7",
"normalize-range": "^0.1.2", "normalize-range": "^0.1.2",
"picocolors": "^1.1.1", "picocolors": "^1.0.1",
"postcss-value-parser": "^4.2.0" "postcss-value-parser": "^4.2.0"
}, },
"bin": { "bin": {
@@ -5781,9 +5781,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001706", "version": "1.0.30001695",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz",
"integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
-4
View File
@@ -1,8 +1,4 @@
[ [
{
"label": "v1.130.0",
"url": "https://v1.130.0.archive.immich.app"
},
{ {
"label": "v1.129.0", "label": "v1.129.0",
"url": "https://v1.129.0.archive.immich.app" "url": "https://v1.129.0.archive.immich.app"
+74 -85
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.130.0", "version": "1.129.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.130.0", "version": "1.129.0",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -15,7 +15,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk", "@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.13.10", "@types/node": "^22.13.9",
"@types/oidc-provider": "^8.5.1", "@types/oidc-provider": "^8.5.1",
"@types/pg": "^8.11.0", "@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
@@ -45,7 +45,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.54", "version": "2.2.53",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -67,7 +67,7 @@
"@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": "^22.13.10", "@types/node": "^22.13.9",
"@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0", "@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^3.0.0", "@vitest/coverage-v8": "^3.0.0",
@@ -95,14 +95,14 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.130.0", "version": "1.129.0",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.10", "@types/node": "^22.13.9",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
}, },
@@ -828,16 +828,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/config-helpers": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
"integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
@@ -889,9 +879,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.22.0", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz",
"integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==", "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -1743,9 +1733,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/oidc-provider": { "node_modules/@types/oidc-provider": {
"version": "8.8.1", "version": "8.8.0",
"resolved": "https://registry.npmjs.org/@types/oidc-provider/-/oidc-provider-8.8.1.tgz", "resolved": "https://registry.npmjs.org/@types/oidc-provider/-/oidc-provider-8.8.0.tgz",
"integrity": "sha512-Yi/OJ7s0CFJ1AWAQrY2EO/zkV9uppLtiGAzrA07lBDveUOvxtYh7GflnHFXcgufVaPxVAjdykizjTYTMNVhdJw==", "integrity": "sha512-9Jtutw4dyAz0PN8EWlxqeNrGHsEZ9EH4QZjfkZIbhmKuiuHIrzoz/S1zHNXX8ogfhWtPp1swMyzXBSo6RTTj1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1892,17 +1882,17 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz",
"integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/scope-manager": "8.26.0",
"@typescript-eslint/type-utils": "8.26.1", "@typescript-eslint/type-utils": "8.26.0",
"@typescript-eslint/utils": "8.26.1", "@typescript-eslint/utils": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@@ -1922,16 +1912,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz",
"integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/scope-manager": "8.26.0",
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@@ -1947,14 +1937,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz",
"integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1" "@typescript-eslint/visitor-keys": "8.26.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1965,14 +1955,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz",
"integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.26.1", "@typescript-eslint/typescript-estree": "8.26.0",
"@typescript-eslint/utils": "8.26.1", "@typescript-eslint/utils": "8.26.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.0.1"
}, },
@@ -1989,9 +1979,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz",
"integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2003,14 +1993,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz",
"integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/visitor-keys": "8.26.1", "@typescript-eslint/visitor-keys": "8.26.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@@ -2056,16 +2046,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz",
"integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.26.1", "@typescript-eslint/scope-manager": "8.26.0",
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"@typescript-eslint/typescript-estree": "8.26.1" "@typescript-eslint/typescript-estree": "8.26.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2080,13 +2070,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.26.1", "version": "8.26.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz",
"integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.26.1", "@typescript-eslint/types": "8.26.0",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
@@ -3167,19 +3157,18 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.22.0", "version": "9.21.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz",
"integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.19.2", "@eslint/config-array": "^0.19.2",
"@eslint/config-helpers": "^0.1.0",
"@eslint/core": "^0.12.0", "@eslint/core": "^0.12.0",
"@eslint/eslintrc": "^3.3.0", "@eslint/eslintrc": "^3.3.0",
"@eslint/js": "9.22.0", "@eslint/js": "9.21.0",
"@eslint/plugin-kit": "^0.2.7", "@eslint/plugin-kit": "^0.2.7",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
@@ -3191,7 +3180,7 @@
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.3.0", "eslint-scope": "^8.2.0",
"eslint-visitor-keys": "^4.2.0", "eslint-visitor-keys": "^4.2.0",
"espree": "^10.3.0", "espree": "^10.3.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
@@ -3319,9 +3308,9 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.3.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
@@ -5222,15 +5211,15 @@
} }
}, },
"node_modules/pg": { "node_modules/pg": {
"version": "8.14.0", "version": "8.13.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.14.0.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.3.tgz",
"integrity": "sha512-nXbVpyoaXVmdqlKEzToFf37qzyeeh7mbiXsnoWvstSqohj88yaa/I/Rq/HEVn2QPSZEuLIJa/jSpRDyzjEx4FQ==", "integrity": "sha512-P6tPt9jXbL9HVu/SSRERNYaYG++MjnscnegFh9pPHihfoBSujsrka0hyuymMzeJKFWrcG8wvCKy8rCe8e5nDUQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"pg-connection-string": "^2.7.0", "pg-connection-string": "^2.7.0",
"pg-pool": "^3.8.0", "pg-pool": "^3.7.1",
"pg-protocol": "^1.8.0", "pg-protocol": "^1.7.1",
"pg-types": "^2.1.0", "pg-types": "^2.1.0",
"pgpass": "1.x" "pgpass": "1.x"
}, },
@@ -5282,9 +5271,9 @@
} }
}, },
"node_modules/pg-pool": { "node_modules/pg-pool": {
"version": "3.8.0", "version": "3.7.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.8.0.tgz", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.1.tgz",
"integrity": "sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==", "integrity": "sha512-xIOsFoh7Vdhojas6q3596mXFsR8nwBQBXX5JiV7p9buEVAGqYL4yFzclON5P9vFrpu1u7Zwl2oriyDa89n0wbw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
@@ -5292,9 +5281,9 @@
} }
}, },
"node_modules/pg-protocol": { "node_modules/pg-protocol": {
"version": "1.8.0", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.8.0.tgz", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.1.tgz",
"integrity": "sha512-jvuYlEkL03NRvOoyoRktBK7+qU5kOvlAwvmrH8sr3wbLrOdVWsRxQfz8mMy9sZFsqJ1hEWNfdWKI4SAmoL+j7g==", "integrity": "sha512-gjTHWGYWsEgy9MsY0Gp6ZJxV24IjDqdpTW7Eh0x+WfJLFsm/TJx1MzL6T0D88mBvkpxotCQ6TwW6N+Kko7lhgQ==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.130.0", "version": "1.129.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
@@ -25,7 +25,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk", "@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.4.2",
"@types/node": "^22.13.10", "@types/node": "^22.13.9",
"@types/oidc-provider": "^8.5.1", "@types/oidc-provider": "^8.5.1",
"@types/pg": "^8.11.0", "@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
+3 -3
View File
@@ -493,7 +493,7 @@ export const utils = {
value: accessToken, value: accessToken,
domain, domain,
path: '/', path: '/',
expires: 2_058_028_213, expires: 1_742_402_728,
httpOnly: true, httpOnly: true,
secure: false, secure: false,
sameSite: 'Lax', sameSite: 'Lax',
@@ -503,7 +503,7 @@ export const utils = {
value: 'password', value: 'password',
domain, domain,
path: '/', path: '/',
expires: 2_058_028_213, expires: 1_742_402_728,
httpOnly: true, httpOnly: true,
secure: false, secure: false,
sameSite: 'Lax', sameSite: 'Lax',
@@ -513,7 +513,7 @@ export const utils = {
value: 'true', value: 'true',
domain, domain,
path: '/', path: '/',
expires: 2_058_028_213, expires: 1_742_402_728,
httpOnly: false, httpOnly: false,
secure: false, secure: false,
sameSite: 'Lax', sameSite: 'Lax',
+1 -1
View File
@@ -45,7 +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('#asset-group-by-date svg');
await page.getByRole('checkbox').click(); await page.getByRole('checkbox').click();
await page.getByRole('button', { name: 'Download' }).click(); await page.getByRole('button', { name: 'Download' }).click();
await page.getByText('DOWNLOADING', { exact: true }).waitFor(); await page.getByText('DOWNLOADING', { exact: true }).waitFor();
-2
View File
@@ -1082,9 +1082,7 @@
"remove_url": "Remove URL", "remove_url": "Remove URL",
"remove_user": "Remove user", "remove_user": "Remove user",
"removed_api_key": "Removed API Key: {name}", "removed_api_key": "Removed API Key: {name}",
"remove_memory": "Remove memory",
"removed_memory": "Removed memory", "removed_memory": "Removed memory",
"remove_photo_from_memory": "Remove photo from this memory",
"removed_photo_from_memory": "Removed photo from memory", "removed_photo_from_memory": "Removed photo from memory",
"removed_from_archive": "Removed from archive", "removed_from_archive": "Removed from archive",
"removed_from_favorites": "Removed from favorites", "removed_from_favorites": "Removed from favorites",
-22
View File
@@ -1,27 +1,5 @@
*.zip *.zip
*.onnx *.onnx
*.rknn
*.npy
*_attr__value
*.weight
*.bias
onnx__*
*in_proj_bias
*.proj
*.latent
*.pos_embed
vocab.txt
export/immich_model_exporter/models/**/README.md
export/**/results/*.json
export/**/root
*.armnn
tokenizer.json
tokenizer_config.json
special_tokens_map.json
preprocess_cfg.json
config.json
merges.txt
vocab.json
upload/ upload/
venv/ venv/
__pycache__/ __pycache__/
+9 -49
View File
@@ -1,6 +1,6 @@
ARG DEVICE=cpu ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:d270285656e8f061ae0004297df62ac4b26d5cef9408b9cc9a5bf08c2cd8d60c AS builder-cpu FROM python:3.11-bookworm@sha256:68a8863d0625f42d47e0684f33ca02f19d6094ef859a8af237aaf645195ed477 AS builder-cpu
FROM builder-cpu AS builder-openvino FROM builder-cpu AS builder-openvino
@@ -15,36 +15,6 @@ RUN mkdir /opt/armnn && \
cd /opt/ann && \ cd /opt/ann && \
sh build.sh sh build.sh
FROM builder-cpu AS builder-rknn
# Warning: 25GiB+ disk space required to pull this image
# TODO: find a way to reduce the image size
FROM rocm/dev-ubuntu-22.04:6.3.4-complete@sha256:1f7e92ca7e3a3785680473329ed1091fc99db3e90fcb3a1688f2933e870ed76b AS builder-rocm
WORKDIR /code
RUN apt-get update && apt-get install -y --no-install-recommends wget git python3.10-venv
RUN wget -nv https://github.com/Kitware/CMake/releases/download/v3.30.1/cmake-3.30.1-linux-x86_64.sh && \
chmod +x cmake-3.30.1-linux-x86_64.sh && \
mkdir -p /code/cmake-3.30.1-linux-x86_64 && \
./cmake-3.30.1-linux-x86_64.sh --skip-license --prefix=/code/cmake-3.30.1-linux-x86_64 && \
rm cmake-3.30.1-linux-x86_64.sh
ENV PATH=/code/cmake-3.30.1-linux-x86_64/bin:${PATH}
RUN git clone --single-branch --branch v1.20.1 --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
# Note: the `parallel` setting uses a substantial amount of RAM
RUN ./build.sh --allow_running_as_root --config Release --build_wheel --update --build --parallel 17 --cmake_extra_defines\
ONNXRUNTIME_VERSION=1.20.1 --skip_tests --use_rocm --rocm_home=/opt/rocm
RUN mv /code/onnxruntime/build/Linux/Release/dist/*.whl /opt/
FROM builder-${DEVICE} AS builder FROM builder-${DEVICE} AS builder
ARG DEVICE ARG DEVICE
@@ -55,25 +25,22 @@ WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y --no-install-recommends g++ RUN apt-get update && apt-get install -y --no-install-recommends g++
COPY --from=ghcr.io/astral-sh/uv:latest@sha256:cb641b1979723dc5ab87d61f079000009edc107d30ae7cbb6e7419fdac044e9f /uv /uvx /bin/ COPY --from=ghcr.io/astral-sh/uv:latest@sha256:562193a4a9d398f8aedddcb223e583da394ee735de36b5815f8f1d22cb49be15 /uv /uvx /bin/
RUN --mount=type=cache,target=/root/.cache/uv \ 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:7029b00486ac40bed03e36775b864d3f3d39dcbdf19cd45e6a52d541e6c178f0 AS prod-cpu FROM python:3.11-slim-bookworm@sha256:614c8691ab74150465ec9123378cd4dde7a6e57be9e558c3108df40664667a4c AS prod-cpu
FROM prod-cpu AS prod-openvino FROM prod-cpu AS prod-openvino
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/igc-1.0.17384.11/intel-igc-core_1.0.17384.11_amd64.deb && \ wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-core_1.0.17384.11_amd64.deb && \
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-opencl_1.0.17384.11_amd64.deb && \ wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17384.11/intel-igc-opencl_1.0.17384.11_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/intel-opencl-icd_24.31.30508.7_amd64.deb && \ wget https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/intel-opencl-icd_24.31.30508.7_amd64.deb && \
wget -nv https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/libigdgmm12_22.4.1_amd64.deb && \ wget https://github.com/intel/compute-runtime/releases/download/24.31.30508.7/libigdgmm12_22.4.1_amd64.deb && \
dpkg -i *.deb && \ dpkg -i *.deb && \
rm *.deb && \ rm *.deb && \
apt-get remove wget -yqq && \ apt-get remove wget -yqq && \
@@ -90,8 +57,6 @@ 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-22.04:6.3.4-complete@sha256:1f7e92ca7e3a3785680473329ed1091fc99db3e90fcb3a1688f2933e870ed76b AS prod-rocm
FROM prod-cpu AS prod-armnn FROM prod-cpu AS prod-armnn
ENV LD_LIBRARY_PATH=/opt/armnn ENV LD_LIBRARY_PATH=/opt/armnn
@@ -112,16 +77,11 @@ COPY --from=builder-armnn \
/opt/ann/build.sh \ /opt/ann/build.sh \
/opt/armnn/ /opt/armnn/
FROM prod-cpu AS prod-rknn
ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/v2.3.0/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so /usr/lib/
FROM prod-${DEVICE} AS prod FROM prod-${DEVICE} AS prod
ARG DEVICE ARG DEVICE
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then echo "libmimalloc2.0"; fi) && \ apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ]; then echo "libmimalloc2.0"; fi) && \
apt-get autoremove -yqq && \ apt-get autoremove -yqq && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -163,4 +123,4 @@ ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE
ENTRYPOINT ["tini", "--"] ENTRYPOINT ["tini", "--"]
CMD ["./start.sh"] CMD ["./start.sh"]
HEALTHCHECK CMD python3 healthcheck.py HEALTHCHECK CMD python3 healthcheck.py
+1 -1
View File
@@ -7,7 +7,7 @@
This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/), so be sure to install it first. This project uses [uv](https://docs.astral.sh/uv/getting-started/installation/), so be sure to install it first.
Running `uv sync --extra cpu` will install everything you need in an isolated virtual environment. Running `uv sync --extra cpu` will install everything you need in an isolated virtual environment.
CUDA, ROCM and OpenVINO are supported as acceleration APIs. To use them, you can replace `--extra cpu` with either of `--extra cuda`, `--extra rocm` or `--extra openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required. CUDA and OpenVINO are supported as acceleration APIs. To use them, you can replace `--group cpu` with either of `--group cuda` or `--group openvino`. In the case of CUDA, a [compute capability](https://developer.nvidia.com/cuda-gpus) of 5.2 or higher is required.
To add or remove dependencies, you can use the commands `uv add $PACKAGE_NAME` and `uv remove $PACKAGE_NAME`, respectively. To add or remove dependencies, you can use the commands `uv add $PACKAGE_NAME` and `uv remove $PACKAGE_NAME`, respectively.
Be sure to commit the `uv.lock` and `pyproject.toml` files with `uv lock` to reflect any changes in dependencies. Be sure to commit the `uv.lock` and `pyproject.toml` files with `uv lock` to reflect any changes in dependencies.
-2
View File
@@ -64,8 +64,6 @@ class Settings(BaseSettings):
ann: bool = True ann: bool = True
ann_fp16_turbo: bool = False ann_fp16_turbo: bool = False
ann_tuning_level: int = 2 ann_tuning_level: int = 2
rknn: bool = True
rknn_threads: int = 1
preload: PreloadModelData | None = None preload: PreloadModelData | None = None
max_batch_size: MaxBatchSize | None = None max_batch_size: MaxBatchSize | None = None
-6
View File
@@ -136,12 +136,6 @@ def ann_session() -> Iterator[mock.Mock]:
yield mocked yield mocked
@pytest.fixture(scope="function")
def rknn_session() -> Iterator[mock.Mock]:
with mock.patch("app.sessions.rknn.RknnPoolExecutor") as mocked:
yield mocked
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def rmtree() -> Iterator[mock.Mock]: def rmtree() -> Iterator[mock.Mock]:
with mock.patch("app.models.base.rmtree", autospec=True) as mocked: with mock.patch("app.models.base.rmtree", autospec=True) as mocked:
+2 -2
View File
@@ -226,9 +226,9 @@ async def load(model: InferenceModel) -> InferenceModel:
except FileNotFoundError as e: except FileNotFoundError as e:
if model.model_format == ModelFormat.ONNX: if model.model_format == ModelFormat.ONNX:
raise e raise e
log.exception(e)
log.warning( log.warning(
f"{model.model_format.upper()} is available, but model '{model.model_name}' does not support it.", f"{model.model_format.upper()} is available, but model '{model.model_name}' does not support it."
exc_info=e,
) )
model.model_format = ModelFormat.ONNX model.model_format = ModelFormat.ONNX
model.load() model.load()
+4 -23
View File
@@ -8,7 +8,6 @@ from typing import Any, ClassVar
from huggingface_hub import snapshot_download from huggingface_hub import snapshot_download
import ann.ann import ann.ann
import app.sessions.rknn as rknn
from app.sessions.ort import OrtSession from app.sessions.ort import OrtSession
from ..config import clean_name, log, settings from ..config import clean_name, log, settings
@@ -67,17 +66,12 @@ class InferenceModel(ABC):
pass pass
def _download(self) -> None: def _download(self) -> None:
ignored_patterns: dict[ModelFormat, list[str]] = { ignore_patterns = [] if self.model_format == ModelFormat.ARMNN else ["*.armnn"]
ModelFormat.ONNX: ["*.armnn", "*.rknn"],
ModelFormat.ARMNN: ["*.rknn"],
ModelFormat.RKNN: ["*.armnn"],
}
snapshot_download( snapshot_download(
f"immich-app/{clean_name(self.model_name)}", f"immich-app/{clean_name(self.model_name)}",
cache_dir=self.cache_dir, cache_dir=self.cache_dir,
local_dir=self.cache_dir, local_dir=self.cache_dir,
ignore_patterns=ignored_patterns.get(self.model_format, []), ignore_patterns=ignore_patterns,
) )
def _load(self) -> ModelSession: def _load(self) -> ModelSession:
@@ -114,25 +108,17 @@ class InferenceModel(ABC):
session: ModelSession = AnnSession(model_path) session: ModelSession = AnnSession(model_path)
case ".onnx": case ".onnx":
session = OrtSession(model_path) session = OrtSession(model_path)
case ".rknn":
session = rknn.RknnSession(model_path)
case _: case _:
raise ValueError(f"Unsupported model file type: {model_path.suffix}") raise ValueError(f"Unsupported model file type: {model_path.suffix}")
return session return session
def model_path_for_format(self, model_format: ModelFormat) -> Path:
model_path_prefix = rknn.model_prefix if model_format == ModelFormat.RKNN else None
if model_path_prefix:
return self.model_dir / model_path_prefix / f"model.{model_format}"
return self.model_dir / f"model.{model_format}"
@property @property
def model_dir(self) -> Path: def model_dir(self) -> Path:
return self.cache_dir / self.model_type.value return self.cache_dir / self.model_type.value
@property @property
def model_path(self) -> Path: def model_path(self) -> Path:
return self.model_path_for_format(self.model_format) return self.model_dir / f"model.{self.model_format}"
@property @property
def model_task(self) -> ModelTask: def model_task(self) -> ModelTask:
@@ -169,9 +155,4 @@ class InferenceModel(ABC):
@property @property
def _model_format_default(self) -> ModelFormat: def _model_format_default(self) -> ModelFormat:
if rknn.is_available: return ModelFormat.ARMNN if ann.ann.is_available and settings.ann else ModelFormat.ONNX
return ModelFormat.RKNN
elif ann.ann.is_available and settings.ann:
return ModelFormat.ARMNN
else:
return ModelFormat.ONNX
+1 -21
View File
@@ -44,18 +44,6 @@ _OPENCLIP_MODELS = {
"nllb-clip-base-siglip__v1", "nllb-clip-base-siglip__v1",
"nllb-clip-large-siglip__mrl", "nllb-clip-large-siglip__mrl",
"nllb-clip-large-siglip__v1", "nllb-clip-large-siglip__v1",
"ViT-B-16-SigLIP2__webli",
"ViT-B-32-SigLIP2-256__webli",
"ViT-L-16-SigLIP2-256__webli",
"ViT-L-16-SigLIP2-384__webli",
"ViT-L-16-SigLIP2-512__webli",
"ViT-SO400M-14-SigLIP2-378__webli",
"ViT-SO400M-14-SigLIP2__webli",
"ViT-SO400M-16-SigLIP2-256__webli",
"ViT-SO400M-16-SigLIP2-384__webli",
"ViT-SO400M-16-SigLIP2-512__webli",
"ViT-gopt-16-SigLIP2-256__webli",
"ViT-gopt-16-SigLIP2-384__webli",
} }
@@ -75,15 +63,7 @@ _INSIGHTFACE_MODELS = {
} }
SUPPORTED_PROVIDERS = [ SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"]
"CUDAExecutionProvider",
"ROCMExecutionProvider",
"OpenVINOExecutionProvider",
"CPUExecutionProvider",
]
RKNN_SUPPORTED_SOCS = ["rk3566", "rk3568", "rk3576", "rk3588"]
RKNN_COREMASK_SUPPORTED_SOCS = ["rk3576", "rk3588"]
def get_model_source(model_name: str) -> ModelSource | None: def get_model_source(model_name: str) -> ModelSource | None:
@@ -31,7 +31,7 @@ class FaceRecognizer(InferenceModel):
self._add_batch_axis(self.model_path) self._add_batch_axis(self.model_path)
session = self._make_session(self.model_path) session = self._make_session(self.model_path)
self.model = ArcFaceONNX( self.model = ArcFaceONNX(
self.model_path_for_format(ModelFormat.ONNX).as_posix(), self.model_path.with_suffix(".onnx").as_posix(),
session=session, session=session,
) )
return session return session
-1
View File
@@ -35,7 +35,6 @@ class ModelType(StrEnum):
class ModelFormat(StrEnum): class ModelFormat(StrEnum):
ARMNN = "armnn" ARMNN = "armnn"
ONNX = "onnx" ONNX = "onnx"
RKNN = "rknn"
class ModelSource(StrEnum): class ModelSource(StrEnum):
+1 -1
View File
@@ -88,7 +88,7 @@ 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 "OpenVINOExecutionProvider": case "OpenVINOExecutionProvider":
options = { options = {
@@ -1,76 +0,0 @@
from __future__ import annotations
from pathlib import Path
from typing import Any, NamedTuple
import numpy as np
from numpy.typing import NDArray
from app.config import log, settings
from app.schemas import SessionNode
from .rknnpool import RknnPoolExecutor, is_available, soc_name
is_available = is_available and settings.rknn
model_prefix = Path("rknpu") / soc_name if is_available and soc_name is not None else None
def run_inference(rknn_lite: Any, input: list[NDArray[np.float32]]) -> list[NDArray[np.float32]]:
outputs: list[NDArray[np.float32]] = rknn_lite.inference(inputs=input, data_format="nchw")
return outputs
input_output_mapping: dict[str, dict[str, Any]] = {
"detection": {
"input": {"norm_tensor:0": (1, 3, 640, 640)},
"output": {
"norm_tensor:1": (12800, 1),
"norm_tensor:2": (3200, 1),
"norm_tensor:3": (800, 1),
"norm_tensor:4": (12800, 4),
"norm_tensor:5": (3200, 4),
"norm_tensor:6": (800, 4),
"norm_tensor:7": (12800, 10),
"norm_tensor:8": (3200, 10),
"norm_tensor:9": (800, 10),
},
},
"recognition": {"input": {"norm_tensor:0": (1, 3, 112, 112)}, "output": {"norm_tensor:1": (1, 512)}},
}
class RknnSession:
def __init__(self, model_path: Path) -> None:
self.model_type = "detection" if "detection" in model_path.parts else "recognition"
self.tpe = settings.rknn_threads
log.info(f"Loading RKNN model from {model_path} with {self.tpe} threads.")
self.rknnpool = RknnPoolExecutor(model_path=model_path.as_posix(), tpes=self.tpe, func=run_inference)
log.info(f"Loaded RKNN model from {model_path} with {self.tpe} threads.")
def get_inputs(self) -> list[SessionNode]:
return [RknnNode(name=k, shape=v) for k, v in input_output_mapping[self.model_type]["input"].items()]
def get_outputs(self) -> list[SessionNode]:
return [RknnNode(name=k, shape=v) for k, v in input_output_mapping[self.model_type]["output"].items()]
def run(
self,
output_names: list[str] | None,
input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]],
run_options: Any = None,
) -> list[NDArray[np.float32]]:
input_data: list[NDArray[np.float32]] = [np.ascontiguousarray(v) for v in input_feed.values()]
self.rknnpool.put(input_data)
res = self.rknnpool.get()
if res is None:
raise RuntimeError("RKNN inference failed!")
return res
class RknnNode(NamedTuple):
name: str | None
shape: tuple[int, ...]
__all__ = ["RknnSession", "RknnNode", "is_available", "soc_name", "model_prefix"]
@@ -1,91 +0,0 @@
# This code is from leafqycc/rknn-multi-threaded
# Following Apache License 2.0
import logging
from concurrent.futures import Future, ThreadPoolExecutor
from pathlib import Path
from queue import Queue
from typing import Callable
import numpy as np
from numpy.typing import NDArray
from app.config import log
from app.models.constants import RKNN_COREMASK_SUPPORTED_SOCS, RKNN_SUPPORTED_SOCS
def get_soc(device_tree_path: Path | str) -> str | None:
try:
with Path(device_tree_path).open() as f:
device_compatible_str = f.read()
for soc in RKNN_SUPPORTED_SOCS:
if soc in device_compatible_str:
return soc
log.warning("Device is not supported for RKNN")
except OSError as e:
log.warning(f"Could not read {device_tree_path}. Reason: %s", e)
return None
soc_name = None
is_available = False
try:
from rknnlite.api import RKNNLite
soc_name = get_soc("/proc/device-tree/compatible")
is_available = soc_name is not None
except ImportError:
log.debug("RKNN is not available")
def init_rknn(model_path: str) -> "RKNNLite":
if not is_available:
raise RuntimeError("rknn is not available!")
rknn_lite = RKNNLite()
rknn_lite.rknn_log.logger.setLevel(logging.ERROR)
ret = rknn_lite.load_rknn(model_path)
if ret != 0:
raise RuntimeError("Failed to load RKNN model")
if soc_name in RKNN_COREMASK_SUPPORTED_SOCS:
ret = rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO)
else:
ret = rknn_lite.init_runtime() # Please do not set this parameter on other platforms.
if ret != 0:
raise RuntimeError("Failed to inititalize RKNN runtime environment")
return rknn_lite
class RknnPoolExecutor:
def __init__(
self,
model_path: str,
tpes: int,
func: Callable[["RKNNLite", list[NDArray[np.float32]]], list[NDArray[np.float32]]],
) -> None:
self.tpes = tpes
self.queue: Queue[Future[list[NDArray[np.float32]]]] = Queue()
self.rknn_pool = [init_rknn(model_path) for _ in range(tpes)]
self.pool = ThreadPoolExecutor(max_workers=tpes)
self.func = func
self.num = 0
def put(self, inputs: list[NDArray[np.float32]]) -> None:
self.queue.put(self.pool.submit(self.func, self.rknn_pool[self.num % self.tpes], inputs))
self.num += 1
def get(self) -> list[NDArray[np.float32]] | None:
if self.queue.empty():
return None
fut = self.queue.get()
return fut.result()
def release(self) -> None:
self.pool.shutdown()
for rknn_lite in self.rknn_pool:
rknn_lite.release()
def __del__(self) -> None:
self.release()
+12 -79
View File
@@ -25,7 +25,6 @@ from app.models.facial_recognition.detection import FaceDetector
from app.models.facial_recognition.recognition import FaceRecognizer from app.models.facial_recognition.recognition import FaceRecognizer
from app.sessions.ann import AnnSession from app.sessions.ann import AnnSession
from app.sessions.ort import OrtSession from app.sessions.ort import OrtSession
from app.sessions.rknn import RknnSession, run_inference
from .config import Settings, settings from .config import Settings, settings
from .models.base import InferenceModel from .models.base import InferenceModel
@@ -70,14 +69,6 @@ class TestBase:
assert encoder.model_format == ModelFormat.ARMNN assert encoder.model_format == ModelFormat.ARMNN
def test_sets_default_model_format_to_rknn_if_available(self, mocker: MockerFixture) -> None:
mocker.patch.object(settings, "rknn", True)
mocker.patch("app.sessions.rknn.is_available", True)
encoder = OpenClipTextualEncoder("ViT-B-32__openai")
assert encoder.model_format == ModelFormat.RKNN
def test_casts_cache_dir_string_to_path(self) -> None: def test_casts_cache_dir_string_to_path(self) -> None:
cache_dir = "/test_cache" cache_dir = "/test_cache"
encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir) encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir)
@@ -134,7 +125,7 @@ class TestBase:
"immich-app/ViT-B-32__openai", "immich-app/ViT-B-32__openai",
cache_dir=encoder.cache_dir, cache_dir=encoder.cache_dir,
local_dir=encoder.cache_dir, local_dir=encoder.cache_dir,
ignore_patterns=["*.armnn", "*.rknn"], ignore_patterns=["*.armnn"],
) )
def test_download_downloads_armnn_if_preferred_format(self, snapshot_download: mock.Mock) -> None: def test_download_downloads_armnn_if_preferred_format(self, snapshot_download: mock.Mock) -> None:
@@ -145,18 +136,7 @@ class TestBase:
"immich-app/ViT-B-32__openai", "immich-app/ViT-B-32__openai",
cache_dir=encoder.cache_dir, cache_dir=encoder.cache_dir,
local_dir=encoder.cache_dir, local_dir=encoder.cache_dir,
ignore_patterns=["*.rknn"], ignore_patterns=[],
)
def test_download_downloads_rknn_if_preferred_format(self, snapshot_download: mock.Mock) -> None:
encoder = OpenClipTextualEncoder("ViT-B-32__openai", model_format=ModelFormat.RKNN)
encoder.download()
snapshot_download.assert_called_once_with(
"immich-app/ViT-B-32__openai",
cache_dir=encoder.cache_dir,
local_dir=encoder.cache_dir,
ignore_patterns=["*.armnn"],
) )
def test_throws_exception_if_model_path_does_not_exist( def test_throws_exception_if_model_path_does_not_exist(
@@ -180,7 +160,6 @@ 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"]
@pytest.mark.providers(CPU_EP) @pytest.mark.providers(CPU_EP)
def test_sets_cpu_provider(self, providers: list[str]) -> None: def test_sets_cpu_provider(self, providers: list[str]) -> None:
@@ -220,12 +199,6 @@ class TestOrtSession:
assert session.providers == self.CUDA_EP assert session.providers == self.CUDA_EP
@pytest.mark.providers(ROCM_EP)
def test_uses_rocm(self, providers: list[str]) -> None:
session = OrtSession("ViT-B-32__openai")
assert session.providers == self.ROCM_EP
def test_sets_provider_kwarg(self) -> None: def test_sets_provider_kwarg(self) -> None:
providers = ["CUDAExecutionProvider"] providers = ["CUDAExecutionProvider"]
session = OrtSession("ViT-B-32__openai", providers=providers) session = OrtSession("ViT-B-32__openai", providers=providers)
@@ -242,33 +215,19 @@ class TestOrtSession:
{"arena_extend_strategy": "kSameAsRequested"}, {"arena_extend_strategy": "kSameAsRequested"},
] ]
def test_sets_provider_options_for_openvino(self) -> None: def test_sets_device_id_for_openvino(self) -> 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"
session = OrtSession(model_path, providers=["OpenVINOExecutionProvider"]) session = OrtSession("ViT-B-32__openai", providers=["OpenVINOExecutionProvider"])
assert session.provider_options == [ assert session.provider_options[0]["device_type"] == "GPU.1"
{
"device_type": "GPU.1",
"precision": "FP32",
"cache_dir": "/cache/ViT-B-32__openai/textual/openvino",
}
]
def test_sets_provider_options_for_cuda(self) -> None: def test_sets_device_id_for_cuda(self) -> None:
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider"]) session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider"])
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}] assert session.provider_options[0]["device_id"] == "1"
def test_sets_provider_options_for_rocm(self) -> None:
os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1"
session = OrtSession("ViT-B-32__openai", providers=["ROCMExecutionProvider"])
assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "device_id": "1"}]
def test_sets_provider_options_kwarg(self) -> None: def test_sets_provider_options_kwarg(self) -> None:
session = OrtSession( session = OrtSession(
@@ -369,33 +328,6 @@ class TestAnnSession:
np_spy.assert_has_calls([mock.call(input1), mock.call(input2)]) np_spy.assert_has_calls([mock.call(input1), mock.call(input2)])
class TestRknnSession:
def test_creates_rknn_session(self, rknn_session: mock.Mock, info: mock.Mock, mocker: MockerFixture) -> None:
model_path = mock.MagicMock(spec=Path)
tpe = 1
mocker.patch("app.sessions.rknn.soc_name", "rk3566")
mocker.patch("app.sessions.rknn.is_available", True)
RknnSession(model_path)
rknn_session.assert_called_once_with(model_path=model_path.as_posix(), tpes=tpe, func=run_inference)
info.assert_has_calls([mock.call(f"Loaded RKNN model from {model_path} with {tpe} threads.")])
def test_run_rknn(self, rknn_session: mock.Mock, mocker: MockerFixture) -> None:
rknn_session.return_value.load.return_value = 123
np_spy = mocker.spy(np, "ascontiguousarray")
mocker.patch("app.sessions.rknn.soc_name", "rk3566")
session = RknnSession(Path("ViT-B-32__openai"))
[input1, input2] = [np.random.rand(1, 3, 224, 224).astype(np.float32) for _ in range(2)]
input_feed = {"input.1": input1, "input.2": input2}
session.run(None, input_feed)
rknn_session.return_value.put.assert_called_once_with([input1, input2])
np_spy.call_count == 2
np_spy.assert_has_calls([mock.call(input1), mock.call(input2)])
class TestCLIP: class TestCLIP:
embedding = np.random.rand(512).astype(np.float32) embedding = np.random.rand(512).astype(np.float32)
cache_dir = Path("test_cache") cache_dir = Path("test_cache")
@@ -897,7 +829,9 @@ class TestLoad:
mock_model.clear_cache.assert_not_called() mock_model.clear_cache.assert_not_called()
mock_model.load.assert_not_called() mock_model.load.assert_not_called()
async def test_falls_back_to_onnx_if_other_format_does_not_exist(self, warning: mock.Mock) -> None: async def test_falls_back_to_onnx_if_other_format_does_not_exist(
self, exception: mock.Mock, warning: mock.Mock
) -> None:
mock_model = mock.Mock(spec=InferenceModel) mock_model = mock.Mock(spec=InferenceModel)
mock_model.model_name = "test_model_name" mock_model.model_name = "test_model_name"
mock_model.model_type = ModelType.VISUAL mock_model.model_type = ModelType.VISUAL
@@ -912,9 +846,8 @@ class TestLoad:
mock_model.clear_cache.assert_not_called() mock_model.clear_cache.assert_not_called()
assert mock_model.load.call_count == 2 assert mock_model.load.call_count == 2
warning.assert_called_once_with( exception.assert_called_once_with(error)
"ARMNN is available, but model 'test_model_name' does not support it.", exc_info=error warning.assert_called_once_with("ARMNN is available, but model 'test_model_name' does not support it.")
)
mock_model.model_format = ModelFormat.ONNX mock_model.model_format = ModelFormat.ONNX
-1
View File
@@ -1 +0,0 @@
3.12
+20
View File
@@ -0,0 +1,20 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:e3797091302382ea841498bc93a7b0a50f7c1448333d5e946d2d1608d0c5f43d AS builder
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \
PYTHONPATH=/usr/src
COPY --chown=$MAMBA_USER:$MAMBA_USER conda-lock.yml /tmp/conda-lock.yml
RUN micromamba install -y -n base -f /tmp/conda-lock.yml && \
micromamba remove -y -n base cxx-compiler && \
micromamba clean --all --yes
WORKDIR /usr/src/app
COPY --chown=$MAMBA_USER:$MAMBA_USER start.sh .
COPY --chown=$MAMBA_USER:$MAMBA_USER app .
ENTRYPOINT ["/usr/local/bin/_entrypoint.sh"]
CMD ["./start.sh"]
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
name: base
channels:
- conda-forge
platforms:
- linux-64
- linux-aarch64
dependencies:
- black
- conda-lock
- mypy
- pytest
- pytest-cov
- pytest-mock
- ruff
category: dev
+25
View File
@@ -0,0 +1,25 @@
name: base
channels:
- conda-forge
- nvidia
- pytorch
platforms:
- linux-64
dependencies:
- cxx-compiler
- onnx==1.*
- onnxruntime==1.*
- open-clip-torch==2.*
- orjson==3.*
- pip
- python==3.11.*
- pytorch>=2.3
- rich==13.*
- safetensors==0.*
- setuptools==68.*
- torchvision
- transformers==4.*
- pip:
- multilingual-clip
- onnxsim
category: main
@@ -1,165 +0,0 @@
import json
import resource
from pathlib import Path
import typer
from tenacity import retry, stop_after_attempt, wait_fixed
from typing_extensions import Annotated
from .exporters.constants import DELETE_PATTERNS, SOURCE_TO_METADATA, ModelSource, ModelTask
from .exporters.onnx import export as onnx_export
from .exporters.rknn import export as rknn_export
app = typer.Typer(pretty_exceptions_show_locals=False)
def generate_readme(model_name: str, model_source: ModelSource) -> str:
(name, link, type) = SOURCE_TO_METADATA[model_source]
match model_source:
case ModelSource.MCLIP:
tags = ["immich", "clip", "multilingual"]
case ModelSource.OPENCLIP:
tags = ["immich", "clip"]
lowered = model_name.lower()
if "xlm" in lowered or "nllb" in lowered:
tags.append("multilingual")
case ModelSource.INSIGHTFACE:
tags = ["immich", "facial-recognition"]
case _:
raise ValueError(f"Unsupported model source {model_source}")
return f"""---
tags:
{" - " + "\n - ".join(tags)}
---
# Model Description
This repo contains ONNX exports for the associated {type} model by {name}. See the [{name}]({link}) repo for more info.
This repo is specifically intended for use with [Immich](https://immich.app/), a self-hosted photo library.
"""
def clean_name(model_name: str) -> str:
hf_model_name = model_name.split("/")[-1]
hf_model_name = hf_model_name.replace("xlm-roberta-large", "XLM-Roberta-Large")
hf_model_name = hf_model_name.replace("xlm-roberta-base", "XLM-Roberta-Base")
return hf_model_name
@app.command()
def export(model_name: str, model_source: ModelSource, output_dir: Path = Path("models"), cache: bool = True) -> None:
hf_model_name = clean_name(model_name)
output_dir = output_dir / hf_model_name
match model_source:
case ModelSource.MCLIP | ModelSource.OPENCLIP:
output_dir.mkdir(parents=True, exist_ok=True)
onnx_export(model_name, model_source, output_dir, cache=cache)
case ModelSource.INSIGHTFACE:
from huggingface_hub import snapshot_download
# TODO: start from insightface dump instead of downloading from HF
snapshot_download(f"immich-app/{hf_model_name}", local_dir=output_dir)
case _:
raise ValueError(f"Unsupported model source {model_source}")
try:
rknn_export(output_dir, cache=cache)
except Exception as e:
print(f"Failed to export model {model_name} to rknn: {e}")
(output_dir / "rknpu").unlink(missing_ok=True)
readme_path = output_dir / "README.md"
if not (cache or readme_path.exists()):
with open(readme_path, "w") as f:
f.write(generate_readme(model_name, model_source))
@app.command()
def profile(model_dir: Path, model_task: ModelTask, output_path: Path) -> None:
from timeit import timeit
import numpy as np
import onnxruntime as ort
np.random.seed(0)
sess_options = ort.SessionOptions()
sess_options.enable_cpu_mem_arena = False
providers = ["CPUExecutionProvider"]
provider_options = [{"arena_extend_strategy": "kSameAsRequested"}]
match model_task:
case ModelTask.SEARCH:
textual = ort.InferenceSession(
model_dir / "textual" / "model.onnx",
sess_options=sess_options,
providers=providers,
provider_options=provider_options,
)
tokens = {node.name: np.random.rand(*node.shape).astype(np.int32) for node in textual.get_inputs()}
visual = ort.InferenceSession(
model_dir / "visual" / "model.onnx",
sess_options=sess_options,
providers=providers,
provider_options=provider_options,
)
image = {node.name: np.random.rand(*node.shape).astype(np.float32) for node in visual.get_inputs()}
def predict() -> None:
textual.run(None, tokens)
visual.run(None, image)
case ModelTask.FACIAL_RECOGNITION:
detection = ort.InferenceSession(
model_dir / "detection" / "model.onnx",
sess_options=sess_options,
providers=providers,
provider_options=provider_options,
)
image = {node.name: np.random.rand(1, 3, 640, 640).astype(np.float32) for node in detection.get_inputs()}
recognition = ort.InferenceSession(
model_dir / "recognition" / "model.onnx",
sess_options=sess_options,
providers=providers,
provider_options=provider_options,
)
face = {node.name: np.random.rand(1, 3, 112, 112).astype(np.float32) for node in recognition.get_inputs()}
def predict() -> None:
detection.run(None, image)
recognition.run(None, face)
case _:
raise ValueError(f"Unsupported model task {model_task}")
predict()
ms = timeit(predict, number=100)
rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
json.dump({"pretrained_model": model_dir.name, "peak_rss": rss, "exec_time_ms": ms}, output_path.open("w"))
print(f"Model {model_dir.name} took {ms:.2f}ms per iteration using {rss / 1024:.2f}MiB of memory")
@app.command()
def upload(
model_dir: Path,
hf_organization: str = "immich-app",
hf_auth_token: Annotated[str | None, typer.Option(envvar="HF_AUTH_TOKEN")] = None,
) -> None:
from huggingface_hub import create_repo, upload_folder
repo_id = f"{hf_organization}/{model_dir.name}"
@retry(stop=stop_after_attempt(5), wait=wait_fixed(5))
def upload_model() -> None:
create_repo(repo_id, exist_ok=True, token=hf_auth_token)
upload_folder(
repo_id=repo_id,
folder_path=model_dir,
# remote repo files to be deleted before uploading
# deletion is in the same commit as the upload, so it's atomic
delete_patterns=DELETE_PATTERNS,
token=hf_auth_token,
)
upload_model()
@@ -1,3 +0,0 @@
from immich_model_exporter import app
app()
@@ -1,54 +0,0 @@
from enum import StrEnum
from typing import NamedTuple
class ModelSource(StrEnum):
INSIGHTFACE = "insightface"
MCLIP = "mclip"
OPENCLIP = "openclip"
class ModelTask(StrEnum):
FACIAL_RECOGNITION = "facial-recognition"
SEARCH = "clip"
class SourceMetadata(NamedTuple):
name: str
link: str
type: str
SOURCE_TO_METADATA = {
ModelSource.MCLIP: SourceMetadata("M-CLIP", "https://huggingface.co/M-CLIP", "CLIP"),
ModelSource.OPENCLIP: SourceMetadata("OpenCLIP", "https://github.com/mlfoundations/open_clip", "CLIP"),
ModelSource.INSIGHTFACE: SourceMetadata(
"InsightFace", "https://github.com/deepinsight/insightface/tree/master", "facial recognition"
),
}
SOURCE_TO_TASK = {
ModelSource.MCLIP: ModelTask.SEARCH,
ModelSource.OPENCLIP: ModelTask.SEARCH,
ModelSource.INSIGHTFACE: ModelTask.FACIAL_RECOGNITION,
}
RKNN_SOCS = ["rk3566", "rk3568", "rk3576", "rk3588"]
# glob to delete old UUID blobs when reuploading models
_uuid_char = "[a-fA-F0-9]"
_uuid_glob = _uuid_char * 8 + "-" + _uuid_char * 4 + "-" + _uuid_char * 4 + "-" + _uuid_char * 4 + "-" + _uuid_char * 12
DELETE_PATTERNS = [
"**/*onnx*",
"**/Constant*",
"**/*.weight",
"**/*.bias",
"**/*.proj",
"**/*in_proj_bias",
"**/*.npy",
"**/*.latent",
"**/*.pos_embed",
f"**/{_uuid_glob}",
]
@@ -1,20 +0,0 @@
from pathlib import Path
from ..constants import ModelSource
from .models import mclip, openclip
def export(
model_name: str, model_source: ModelSource, output_dir: Path, opset_version: int = 19, cache: bool = True
) -> None:
visual_dir = output_dir / "visual"
textual_dir = output_dir / "textual"
match model_source:
case ModelSource.MCLIP:
mclip.to_onnx(model_name, opset_version, visual_dir, textual_dir, cache=cache)
case ModelSource.OPENCLIP:
name, _, pretrained = model_name.partition("__")
config = openclip.OpenCLIPModelConfig(name, pretrained)
openclip.to_onnx(config, opset_version, visual_dir, textual_dir, cache=cache)
case _:
raise ValueError(f"Unsupported model source {model_source}")
@@ -1,151 +0,0 @@
import warnings
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import Any
from .util import get_model_path, save_config
@dataclass
class OpenCLIPModelConfig:
name: str
pretrained: str
@cached_property
def model_config(self) -> dict[str, Any]:
import open_clip
config: dict[str, Any] | None = open_clip.get_model_config(self.name)
if config is None:
raise ValueError(f"Unknown model {self.name}")
return config
@property
def image_size(self) -> int:
image_size: int = self.model_config["vision_cfg"]["image_size"]
return image_size
@property
def sequence_length(self) -> int:
context_length: int = self.model_config["text_cfg"].get("context_length", 77)
return context_length
def to_onnx(
model_cfg: OpenCLIPModelConfig,
opset_version: int,
output_dir_visual: Path | str | None = None,
output_dir_textual: Path | str | None = None,
cache: bool = True,
) -> tuple[Path | None, Path | None]:
visual_path = None
textual_path = None
if output_dir_visual is not None:
output_dir_visual = Path(output_dir_visual)
visual_path = get_model_path(output_dir_visual)
if output_dir_textual is not None:
output_dir_textual = Path(output_dir_textual)
textual_path = get_model_path(output_dir_textual)
if cache and ((textual_path is None or textual_path.exists()) and (visual_path is None or visual_path.exists())):
print(f"Models {textual_path} and {visual_path} already exist, skipping")
return visual_path, textual_path
import open_clip
import torch
from transformers import AutoTokenizer
torch.backends.mha.set_fastpath_enabled(False)
model = open_clip.create_model(
model_cfg.name,
pretrained=model_cfg.pretrained,
jit=False,
require_pretrained=True,
)
text_vision_cfg = open_clip.get_model_config(model_cfg.name)
model.eval()
for param in model.parameters():
param.requires_grad_(False)
if visual_path is not None and output_dir_visual is not None:
if not cache or not visual_path.exists():
save_config(
open_clip.get_model_preprocess_cfg(model),
output_dir_visual / "preprocess_cfg.json",
)
save_config(text_vision_cfg, output_dir_visual.parent / "config.json")
_export_image_encoder(model, model_cfg, visual_path, opset_version)
else:
print(f"Model {visual_path} already exists, skipping")
if textual_path is not None and output_dir_textual is not None:
if not cache or not textual_path.exists():
tokenizer_name = text_vision_cfg["text_cfg"].get("hf_tokenizer_name", "openai/clip-vit-base-patch32")
AutoTokenizer.from_pretrained(tokenizer_name).save_pretrained(output_dir_textual)
_export_text_encoder(model, model_cfg, textual_path, opset_version)
else:
print(f"Model {textual_path} already exists, skipping")
return visual_path, textual_path
def _export_image_encoder(
model: Any, model_cfg: OpenCLIPModelConfig, output_path: Path | str, opset_version: int
) -> None:
import torch
output_path = Path(output_path)
def encode_image(image: torch.Tensor) -> torch.Tensor:
output = model.encode_image(image, normalize=True)
assert isinstance(output, torch.Tensor)
return output
model.forward = encode_image
args = (torch.randn(1, 3, model_cfg.image_size, model_cfg.image_size),)
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
torch.onnx.export(
model,
args,
output_path.as_posix(),
input_names=["image"],
output_names=["embedding"],
opset_version=opset_version,
# dynamic_axes={"image": {0: "batch_size"}},
)
def _export_text_encoder(
model: Any, model_cfg: OpenCLIPModelConfig, output_path: Path | str, opset_version: int
) -> None:
import torch
output_path = Path(output_path)
def encode_text(text: torch.Tensor) -> torch.Tensor:
output = model.encode_text(text, normalize=True)
assert isinstance(output, torch.Tensor)
return output
model.forward = encode_text
args = (torch.ones(1, model_cfg.sequence_length, dtype=torch.int32),)
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
torch.onnx.export(
model,
args,
output_path.as_posix(),
input_names=["text"],
output_names=["embedding"],
opset_version=opset_version,
# dynamic_axes={"text": {0: "batch_size"}},
)
@@ -1,96 +0,0 @@
from pathlib import Path
from .constants import RKNN_SOCS
def _export_platform(
model_dir: Path,
target_platform: str,
inputs: list[str] | None = None,
input_size_list: list[list[int]] | None = None,
fuse_matmul_softmax_matmul_to_sdpa: bool = True,
cache: bool = True,
) -> None:
from rknn.api import RKNN
input_path = model_dir / "model.onnx"
output_path = model_dir / "rknpu" / target_platform / "model.rknn"
if cache and output_path.exists():
print(f"Model {input_path} already exists at {output_path}, skipping")
return
print(f"Exporting model {input_path} to {output_path}")
rknn = RKNN(verbose=False)
rknn.config(
target_platform=target_platform,
disable_rules=["fuse_matmul_softmax_matmul_to_sdpa"] if not fuse_matmul_softmax_matmul_to_sdpa else [],
enable_flash_attention=False,
model_pruning=True,
)
ret = rknn.load_onnx(model=input_path.as_posix(), inputs=inputs, input_size_list=input_size_list)
if ret != 0:
raise RuntimeError("Load failed!")
ret = rknn.build(do_quantization=False)
if ret != 0:
raise RuntimeError("Build failed!")
output_path.parent.mkdir(parents=True, exist_ok=True)
ret = rknn.export_rknn(output_path.as_posix())
if ret != 0:
raise RuntimeError("Export rknn model failed!")
def _export_platforms(
model_dir: Path,
inputs: list[str] | None = None,
input_size_list: list[list[int]] | None = None,
cache: bool = True,
) -> None:
fuse_matmul_softmax_matmul_to_sdpa = True
for soc in RKNN_SOCS:
try:
_export_platform(
model_dir,
soc,
inputs=inputs,
input_size_list=input_size_list,
fuse_matmul_softmax_matmul_to_sdpa=fuse_matmul_softmax_matmul_to_sdpa,
cache=cache,
)
except Exception as e:
print(f"Failed to export model for {soc}: {e}")
if "inputs or 'outputs' must be set" in str(e):
print("Retrying without fuse_matmul_softmax_matmul_to_sdpa")
fuse_matmul_softmax_matmul_to_sdpa = False
_export_platform(
model_dir,
soc,
inputs=inputs,
input_size_list=input_size_list,
fuse_matmul_softmax_matmul_to_sdpa=fuse_matmul_softmax_matmul_to_sdpa,
cache=cache,
)
def export(model_dir: Path, cache: bool = True) -> None:
textual = model_dir / "textual"
visual = model_dir / "visual"
detection = model_dir / "detection"
recognition = model_dir / "recognition"
if textual.is_dir():
_export_platforms(textual, cache=cache)
if visual.is_dir():
_export_platforms(visual, cache=cache)
if detection.is_dir():
_export_platforms(detection, inputs=["input.1"], input_size_list=[[1, 3, 640, 640]], cache=cache)
if recognition.is_dir():
_export_platforms(recognition, inputs=["input.1"], input_size_list=[[1, 3, 112, 112]], cache=cache)
@@ -1,22 +0,0 @@
import json
from pathlib import Path
models_dir = Path("models")
model_to_embed_dim = {}
for model_dir in models_dir.iterdir():
if not model_dir.is_dir():
continue
config_path = model_dir / "config.json"
if not config_path.exists():
print(f"Skipping {model_dir.name} as it does not have a config.json")
continue
with open(config_path) as f:
config = json.load(f)
embed_dim = config.get("embed_dim")
if embed_dim is None:
print(f"Skipping {model_dir.name} as it does not have an embed_dim")
continue
print(f"{model_dir.name}: {embed_dim}")
model_to_embed_dim[model_dir.name] = {"dimSize": embed_dim}
print(json.dumps(model_to_embed_dim))
@@ -1,121 +0,0 @@
import polars as pl
def collapsed_table(language: str, df: pl.DataFrame) -> str:
with pl.Config(
tbl_formatting="ASCII_MARKDOWN",
tbl_hide_column_data_types=True,
tbl_hide_dataframe_shape=True,
fmt_str_lengths=100,
tbl_rows=1000,
tbl_width_chars=1000,
):
return f"<details>\n<summary>{language}</summary>\n{str(df)}\n</details>"
languages = {
"en": "English",
"ar": "Arabic",
"bn": "Bengali",
"zh": "Chinese (Simplified)",
"hr": "Croatian",
"quz": "Cusco Quechua",
"cs": "Czech",
"da": "Danish",
"nl": "Dutch",
"fil": "Filipino",
"fi": "Finnish",
"fr": "French",
"de": "German",
"el": "Greek",
"he": "Hebrew",
"hi": "Hindi",
"hu": "Hungarian",
"id": "Indonesian",
"it": "Italian",
"ja": "Japanese",
"ko": "Korean",
"mi": "Maori",
"no": "Norwegian",
"fa": "Persian",
"pl": "Polish",
"pt": "Portuguese",
"ro": "Romanian",
"ru": "Russian",
"es": "Spanish",
"sw": "Swahili",
"sv": "Swedish",
"te": "Telugu",
"th": "Thai",
"tr": "Turkish",
"uk": "Ukrainian",
"vi": "Vietnamese",
}
profile_df = pl.scan_ndjson("profiling/*.json").select("pretrained_model", "peak_rss", "exec_time_ms")
eval_df = pl.scan_ndjson("results/*.json").select("model", "pretrained", "language", "metrics")
eval_df = eval_df.with_columns(
model=pl.col("model")
.str.replace("xlm-roberta-base", "XLM-Roberta-Base")
.str.replace("xlm-roberta-large", "XLM-Roberta-Large")
)
eval_df = eval_df.with_columns(pretrained_model=pl.concat_str(pl.col("model"), pl.col("pretrained"), separator="__"))
eval_df = eval_df.drop("model", "pretrained")
eval_df = eval_df.join(profile_df, on="pretrained_model")
eval_df = eval_df.with_columns(
recall=(
pl.col("metrics").struct.field("image_retrieval_recall@1")
+ pl.col("metrics").struct.field("image_retrieval_recall@5")
+ pl.col("metrics").struct.field("image_retrieval_recall@10")
)
* (100 / 3)
)
pareto_front = eval_df.join_where(
eval_df.select("language", "peak_rss", "exec_time_ms", "recall").rename(
{
"language": "language_other",
"peak_rss": "peak_rss_other",
"exec_time_ms": "exec_time_ms_other",
"recall": "recall_other",
}
),
(pl.col("language") == pl.col("language_other"))
& (pl.col("peak_rss_other") <= pl.col("peak_rss"))
& (pl.col("exec_time_ms_other") <= pl.col("exec_time_ms"))
& (pl.col("recall_other") >= pl.col("recall"))
& (
(pl.col("peak_rss_other") < pl.col("peak_rss"))
| (pl.col("exec_time_ms_other") < pl.col("exec_time_ms"))
| (pl.col("recall_other") > pl.col("recall"))
),
)
eval_df = eval_df.join(pareto_front, on=["pretrained_model", "language"], how="left")
eval_df = eval_df.with_columns(is_pareto=pl.col("recall_other").is_null())
eval_df = (
eval_df.drop("peak_rss_other", "exec_time_ms_other", "recall_other", "language_other")
.unique(subset=["pretrained_model", "language"])
.collect()
)
eval_df.write_parquet("model_info.parquet")
eval_df = eval_df.drop("metrics")
eval_df = eval_df.filter(pl.col("recall") >= 20)
eval_df = eval_df.sort("recall", descending=True)
eval_df = eval_df.select(
pl.col("pretrained_model").alias("Model"),
(pl.col("peak_rss") / 1024).round().cast(pl.UInt32).alias("Memory (MiB)"),
pl.col("exec_time_ms").round(2).alias("Execution Time (ms)"),
pl.col("language").alias("Language"),
pl.col("recall").round(2).alias("Recall (%)"),
pl.when(pl.col("is_pareto")).then(pl.lit("")).otherwise(pl.lit("")).alias("Pareto Optimal"),
)
for language in languages:
lang_df = eval_df.filter(pl.col("Language") == language).drop("Language")
if lang_df.shape[0] == 0:
continue
print(collapsed_table(languages[language], lang_df))
@@ -1,171 +0,0 @@
import subprocess
from pathlib import Path
from exporters.constants import ModelSource
from immich_model_exporter import clean_name
from immich_model_exporter.exporters.constants import SOURCE_TO_TASK
mclip = [
"M-CLIP/LABSE-Vit-L-14",
"M-CLIP/XLM-Roberta-Large-Vit-B-16Plus",
"M-CLIP/XLM-Roberta-Large-Vit-B-32",
"M-CLIP/XLM-Roberta-Large-Vit-L-14",
]
openclip = [
"RN101__openai",
"RN101__yfcc15m",
"RN50__cc12m",
"RN50__openai",
"RN50__yfcc15m",
"RN50x16__openai",
"RN50x4__openai",
"RN50x64__openai",
"ViT-B-16-SigLIP-256__webli",
"ViT-B-16-SigLIP-384__webli",
"ViT-B-16-SigLIP-512__webli",
"ViT-B-16-SigLIP-i18n-256__webli",
"ViT-B-16-SigLIP2__webli",
"ViT-B-16-SigLIP__webli",
"ViT-B-16-plus-240__laion400m_e31",
"ViT-B-16-plus-240__laion400m_e32",
"ViT-B-16__laion400m_e31",
"ViT-B-16__laion400m_e32",
"ViT-B-16__openai",
"ViT-B-32-SigLIP2-256__webli",
"ViT-B-32__laion2b-s34b-b79k",
"ViT-B-32__laion2b_e16",
"ViT-B-32__laion400m_e31",
"ViT-B-32__laion400m_e32",
"ViT-B-32__openai",
"ViT-H-14-378-quickgelu__dfn5b",
"ViT-H-14-quickgelu__dfn5b",
"ViT-H-14__laion2b-s32b-b79k",
"ViT-L-14-336__openai",
"ViT-L-14-quickgelu__dfn2b",
"ViT-L-14__laion2b-s32b-b82k",
"ViT-L-14__laion400m_e31",
"ViT-L-14__laion400m_e32",
"ViT-L-14__openai",
"ViT-L-16-SigLIP-256__webli",
"ViT-L-16-SigLIP-384__webli",
"ViT-L-16-SigLIP2-256__webli",
"ViT-L-16-SigLIP2-384__webli",
"ViT-L-16-SigLIP2-512__webli",
"ViT-SO400M-14-SigLIP-384__webli",
"ViT-SO400M-14-SigLIP2-378__webli",
"ViT-SO400M-14-SigLIP2__webli",
"ViT-SO400M-16-SigLIP2-256__webli",
"ViT-SO400M-16-SigLIP2-384__webli",
"ViT-SO400M-16-SigLIP2-512__webli",
"ViT-gopt-16-SigLIP2-256__webli",
"ViT-gopt-16-SigLIP2-384__webli",
"nllb-clip-base-siglip__mrl",
"nllb-clip-base-siglip__v1",
"nllb-clip-large-siglip__mrl",
"nllb-clip-large-siglip__v1",
"xlm-roberta-base-ViT-B-32__laion5b_s13b_b90k",
"xlm-roberta-large-ViT-H-14__frozen_laion5b_s13b_b90k",
]
insightface = [
"antelopev2",
"buffalo_l",
"buffalo_m",
"buffalo_s",
]
def export_models(models: list[str], source: ModelSource) -> None:
profiling_dir = Path("profiling")
profiling_dir.mkdir(exist_ok=True)
for model in models:
try:
model_dir = f"models/{clean_name(model)}"
task = SOURCE_TO_TASK[source]
print(f"Processing model {model}")
subprocess.check_call(["python", "-m", "immich_model_exporter", "export", model, source])
subprocess.check_call(
[
"python",
"-m",
"immich_model_exporter",
"profile",
model_dir,
task,
"--output_path",
profiling_dir / f"{model}.json",
]
)
subprocess.check_call(["python", "-m", "immich_model_exporter", "upload", model_dir])
except Exception as e:
print(f"Failed to export model {model}: {e}")
if __name__ == "__main__":
export_models(mclip, ModelSource.MCLIP)
export_models(openclip, ModelSource.OPENCLIP)
export_models(insightface, ModelSource.INSIGHTFACE)
Path("results").mkdir(exist_ok=True)
subprocess.check_call(
[
"python",
"clip_benchmark",
"eval",
"--pretrained_model",
*[name.replace("__", ",") for name in openclip],
"--task",
"zeroshot_retrieval",
"--dataset",
"crossmodal3600",
"--batch_size",
"64",
"--language",
"ar",
"bn",
"cs",
"da",
"de",
"el",
"en",
"es",
"fa",
"fi",
"fil",
"fr",
"he",
"hi",
"hr",
"hu",
"id",
"it",
"ja",
"ko",
"mi",
"nl",
"no",
"pl",
"pt",
"quz",
"ro",
"ru",
"sv",
"sw",
"te",
"th",
"tr",
"uk",
"vi",
"zh",
"--recall_k",
"1",
"5",
"10",
"--no_amp",
"--output",
"results/{dataset}_{language}_{model}_{pretrained}.json",
]
)
@@ -1,6 +1,11 @@
import os
import tempfile
import warnings import warnings
from pathlib import Path from pathlib import Path
from typing import Any
import torch
from multilingual_clip.pt_multilingual_clip import MultilingualCLIP
from transformers import AutoTokenizer
from .openclip import OpenCLIPModelConfig from .openclip import OpenCLIPModelConfig
from .openclip import to_onnx as openclip_to_onnx from .openclip import to_onnx as openclip_to_onnx
@@ -16,38 +21,25 @@ _MCLIP_TO_OPENCLIP = {
def to_onnx( def to_onnx(
model_name: str, model_name: str,
opset_version: int,
output_dir_visual: Path | str, output_dir_visual: Path | str,
output_dir_textual: Path | str, output_dir_textual: Path | str,
cache: bool = True,
) -> tuple[Path, Path]: ) -> tuple[Path, Path]:
textual_path = get_model_path(output_dir_textual) textual_path = get_model_path(output_dir_textual)
if not cache or not textual_path.exists(): with tempfile.TemporaryDirectory() as tmpdir:
import torch model = MultilingualCLIP.from_pretrained(model_name, cache_dir=os.environ.get("CACHE_DIR", tmpdir))
from multilingual_clip.pt_multilingual_clip import MultilingualCLIP
from transformers import AutoTokenizer
torch.backends.mha.set_fastpath_enabled(False)
model = MultilingualCLIP.from_pretrained(model_name)
AutoTokenizer.from_pretrained(model_name).save_pretrained(output_dir_textual) AutoTokenizer.from_pretrained(model_name).save_pretrained(output_dir_textual)
model.eval() model.eval()
for param in model.parameters(): for param in model.parameters():
param.requires_grad_(False) param.requires_grad_(False)
_export_text_encoder(model, textual_path, opset_version) export_text_encoder(model, textual_path)
else: visual_path, _ = openclip_to_onnx(_MCLIP_TO_OPENCLIP[model_name], output_dir_visual)
print(f"Model {textual_path} already exists, skipping") assert visual_path is not None, "Visual model export failed"
visual_path, _ = openclip_to_onnx(_MCLIP_TO_OPENCLIP[model_name], opset_version, output_dir_visual, cache=cache)
assert visual_path is not None, "Visual model export failed"
return visual_path, textual_path return visual_path, textual_path
def _export_text_encoder(model: Any, output_path: Path | str, opset_version: int) -> None: def export_text_encoder(model: MultilingualCLIP, output_path: Path | str) -> None:
import torch
from multilingual_clip.pt_multilingual_clip import MultilingualCLIP
output_path = Path(output_path) output_path = Path(output_path)
def forward(self: MultilingualCLIP, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor: def forward(self: MultilingualCLIP, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
@@ -69,7 +61,7 @@ def _export_text_encoder(model: Any, output_path: Path | str, opset_version: int
output_path.as_posix(), output_path.as_posix(),
input_names=["input_ids", "attention_mask"], input_names=["input_ids", "attention_mask"],
output_names=["embedding"], output_names=["embedding"],
opset_version=opset_version, opset_version=17,
# dynamic_axes={ # dynamic_axes={
# "input_ids": {0: "batch_size", 1: "sequence_length"}, # "input_ids": {0: "batch_size", 1: "sequence_length"},
# "attention_mask": {0: "batch_size", 1: "sequence_length"}, # "attention_mask": {0: "batch_size", 1: "sequence_length"},
+114
View File
@@ -0,0 +1,114 @@
import os
import tempfile
import warnings
from dataclasses import dataclass, field
from pathlib import Path
import open_clip
import torch
from transformers import AutoTokenizer
from .util import get_model_path, save_config
@dataclass
class OpenCLIPModelConfig:
name: str
pretrained: str
image_size: int = field(init=False)
sequence_length: int = field(init=False)
def __post_init__(self) -> None:
open_clip_cfg = open_clip.get_model_config(self.name)
if open_clip_cfg is None:
raise ValueError(f"Unknown model {self.name}")
self.image_size = open_clip_cfg["vision_cfg"]["image_size"]
self.sequence_length = open_clip_cfg["text_cfg"].get("context_length", 77)
def to_onnx(
model_cfg: OpenCLIPModelConfig,
output_dir_visual: Path | str | None = None,
output_dir_textual: Path | str | None = None,
) -> tuple[Path | None, Path | None]:
visual_path = None
textual_path = None
with tempfile.TemporaryDirectory() as tmpdir:
model = open_clip.create_model(
model_cfg.name,
pretrained=model_cfg.pretrained,
jit=False,
cache_dir=os.environ.get("CACHE_DIR", tmpdir),
require_pretrained=True,
)
text_vision_cfg = open_clip.get_model_config(model_cfg.name)
model.eval()
for param in model.parameters():
param.requires_grad_(False)
if output_dir_visual is not None:
output_dir_visual = Path(output_dir_visual)
visual_path = get_model_path(output_dir_visual)
save_config(open_clip.get_model_preprocess_cfg(model), output_dir_visual / "preprocess_cfg.json")
save_config(text_vision_cfg, output_dir_visual.parent / "config.json")
export_image_encoder(model, model_cfg, visual_path)
if output_dir_textual is not None:
output_dir_textual = Path(output_dir_textual)
textual_path = get_model_path(output_dir_textual)
tokenizer_name = text_vision_cfg["text_cfg"].get("hf_tokenizer_name", "openai/clip-vit-base-patch32")
AutoTokenizer.from_pretrained(tokenizer_name).save_pretrained(output_dir_textual)
export_text_encoder(model, model_cfg, textual_path)
return visual_path, textual_path
def export_image_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, output_path: Path | str) -> None:
output_path = Path(output_path)
def encode_image(image: torch.Tensor) -> torch.Tensor:
output = model.encode_image(image, normalize=True)
assert isinstance(output, torch.Tensor)
return output
args = (torch.randn(1, 3, model_cfg.image_size, model_cfg.image_size),)
traced = torch.jit.trace(encode_image, args) # type: ignore[no-untyped-call]
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
torch.onnx.export(
traced,
args,
output_path.as_posix(),
input_names=["image"],
output_names=["embedding"],
opset_version=17,
# dynamic_axes={"image": {0: "batch_size"}},
)
def export_text_encoder(model: open_clip.CLIP, model_cfg: OpenCLIPModelConfig, output_path: Path | str) -> None:
output_path = Path(output_path)
def encode_text(text: torch.Tensor) -> torch.Tensor:
output = model.encode_text(text, normalize=True)
assert isinstance(output, torch.Tensor)
return output
args = (torch.ones(1, model_cfg.sequence_length, dtype=torch.int32),)
traced = torch.jit.trace(encode_text, args) # type: ignore[no-untyped-call]
with warnings.catch_warnings():
warnings.simplefilter("ignore", UserWarning)
torch.onnx.export(
traced,
args,
output_path.as_posix(),
input_names=["text"],
output_names=["embedding"],
opset_version=17,
# dynamic_axes={"text": {0: "batch_size"}},
)
@@ -0,0 +1,49 @@
from pathlib import Path
import onnx
import onnxruntime as ort
import onnxsim
def save_onnx(model: onnx.ModelProto, output_path: Path | str) -> None:
try:
onnx.save(model, output_path)
except ValueError as e:
if "The proto size is larger than the 2 GB limit." in str(e):
onnx.save(model, output_path, save_as_external_data=True, size_threshold=1_000_000)
else:
raise e
def optimize_onnxsim(model_path: Path | str, output_path: Path | str) -> None:
model_path = Path(model_path)
output_path = Path(output_path)
model = onnx.load(model_path.as_posix())
model, check = onnxsim.simplify(model)
assert check, "Simplified ONNX model could not be validated"
for file in model_path.parent.iterdir():
if file.name.startswith("Constant") or "onnx" in file.name or file.suffix == ".weight":
file.unlink()
save_onnx(model, output_path)
def optimize_ort(
model_path: Path | str,
output_path: Path | str,
level: ort.GraphOptimizationLevel = ort.GraphOptimizationLevel.ORT_ENABLE_BASIC,
) -> None:
model_path = Path(model_path)
output_path = Path(output_path)
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = level
sess_options.optimized_model_filepath = output_path.as_posix()
ort.InferenceSession(model_path.as_posix(), providers=["CPUExecutionProvider"], sess_options=sess_options)
def optimize(model_path: Path | str) -> None:
model_path = Path(model_path)
optimize_ort(model_path, model_path)
optimize_onnxsim(model_path, model_path)
-60
View File
@@ -1,60 +0,0 @@
[project]
name = "immich_model_exporter"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10, <4.0"
dependencies = [
"huggingface-hub>=0.29.3",
"multilingual-clip>=1.0.10",
"onnx>=1.14.1",
"onnxruntime>=1.16.0",
"open-clip-torch>=2.31.0",
"typer>=0.15.2",
"rknn-toolkit2>=2.3.0",
"transformers>=4.49.0",
"tenacity>=9.0.0",
"clip-benchmark>=1.6.1",
"polars>=1.25.2",
]
[dependency-groups]
dev = ["black>=23.3.0", "mypy>=1.3.0", "ruff>=0.0.272"]
[tool.uv]
override-dependencies = [
"onnx>=1.16.0,<2",
"onnxruntime>=1.18.2,<2",
"torch>=2.4",
"torchvision>=0.21",
]
[tool.hatch.build.targets.sdist]
include = ["immich_model_exporter"]
[tool.hatch.build.targets.wheel]
include = ["immich_model_exporter"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.mypy]
python_version = "3.12"
follow_imports = "silent"
warn_redundant_casts = true
disallow_any_generics = true
check_untyped_defs = true
disallow_untyped_defs = true
ignore_missing_imports = true
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I"]
[tool.black]
line-length = 120
target-version = ['py312']
+113
View File
@@ -0,0 +1,113 @@
import gc
import os
from pathlib import Path
from tempfile import TemporaryDirectory
import torch
from huggingface_hub import create_repo, upload_folder
from models import mclip, openclip
from models.optimize import optimize
from rich.progress import Progress
models = [
"M-CLIP/LABSE-Vit-L-14",
"M-CLIP/XLM-Roberta-Large-Vit-B-16Plus",
"M-CLIP/XLM-Roberta-Large-Vit-B-32",
"M-CLIP/XLM-Roberta-Large-Vit-L-14",
"RN101::openai",
"RN101::yfcc15m",
"RN50::cc12m",
"RN50::openai",
"RN50::yfcc15m",
"RN50x16::openai",
"RN50x4::openai",
"RN50x64::openai",
"ViT-B-16-SigLIP-256::webli",
"ViT-B-16-SigLIP-384::webli",
"ViT-B-16-SigLIP-512::webli",
"ViT-B-16-SigLIP-i18n-256::webli",
"ViT-B-16-SigLIP::webli",
"ViT-B-16-plus-240::laion400m_e31",
"ViT-B-16-plus-240::laion400m_e32",
"ViT-B-16::laion400m_e31",
"ViT-B-16::laion400m_e32",
"ViT-B-16::openai",
"ViT-B-32::laion2b-s34b-b79k",
"ViT-B-32::laion2b_e16",
"ViT-B-32::laion400m_e31",
"ViT-B-32::laion400m_e32",
"ViT-B-32::openai",
"ViT-H-14-378-quickgelu::dfn5b",
"ViT-H-14-quickgelu::dfn5b",
"ViT-H-14::laion2b-s32b-b79k",
"ViT-L-14-336::openai",
"ViT-L-14-quickgelu::dfn2b",
"ViT-L-14::laion2b-s32b-b82k",
"ViT-L-14::laion400m_e31",
"ViT-L-14::laion400m_e32",
"ViT-L-14::openai",
"ViT-L-16-SigLIP-256::webli",
"ViT-L-16-SigLIP-384::webli",
"ViT-SO400M-14-SigLIP-384::webli",
"ViT-g-14::laion2b-s12b-b42k",
"nllb-clip-base-siglip::mrl",
"nllb-clip-base-siglip::v1",
"nllb-clip-large-siglip::mrl",
"nllb-clip-large-siglip::v1",
"xlm-roberta-base-ViT-B-32::laion5b_s13b_b90k",
"xlm-roberta-large-ViT-H-14::frozen_laion5b_s13b_b90k",
]
# glob to delete old UUID blobs when reuploading models
uuid_char = "[a-fA-F0-9]"
uuid_glob = uuid_char * 8 + "-" + uuid_char * 4 + "-" + uuid_char * 4 + "-" + uuid_char * 4 + "-" + uuid_char * 12
# remote repo files to be deleted before uploading
# deletion is in the same commit as the upload, so it's atomic
delete_patterns = ["**/*onnx*", "**/Constant*", "**/*.weight", "**/*.bias", f"**/{uuid_glob}"]
with Progress() as progress:
task = progress.add_task("[green]Exporting models...", total=len(models))
token = os.environ.get("HF_AUTH_TOKEN")
torch.backends.mha.set_fastpath_enabled(False)
with TemporaryDirectory() as tmp:
tmpdir = Path(tmp)
for model in models:
model_name = model.split("/")[-1].replace("::", "__")
hf_model_name = model_name.replace("xlm-roberta-large", "XLM-Roberta-Large")
hf_model_name = model_name.replace("xlm-roberta-base", "XLM-Roberta-Base")
config_path = tmpdir / model_name / "config.json"
def export() -> None:
progress.update(task, description=f"[green]Exporting {hf_model_name}")
visual_dir = tmpdir / hf_model_name / "visual"
textual_dir = tmpdir / hf_model_name / "textual"
if model.startswith("M-CLIP"):
visual_path, textual_path = mclip.to_onnx(model, visual_dir, textual_dir)
else:
name, _, pretrained = model_name.partition("__")
config = openclip.OpenCLIPModelConfig(name, pretrained)
visual_path, textual_path = openclip.to_onnx(config, visual_dir, textual_dir)
progress.update(task, description=f"[green]Optimizing {hf_model_name} (visual)")
optimize(visual_path)
progress.update(task, description=f"[green]Optimizing {hf_model_name} (textual)")
optimize(textual_path)
gc.collect()
def upload() -> None:
progress.update(task, description=f"[yellow]Uploading {hf_model_name}")
repo_id = f"immich-app/{hf_model_name}"
create_repo(repo_id, exist_ok=True)
upload_folder(
repo_id=repo_id,
folder_path=tmpdir / hf_model_name,
delete_patterns=delete_patterns,
token=token,
)
export()
if token is not None:
upload()
progress.update(task, advance=1)
-1924
View File
File diff suppressed because it is too large Load Diff
@@ -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,13 +0,0 @@
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index d90a2a355..bb1a7de12 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -295,7 +295,7 @@ if (onnxruntime_USE_ROCM)
endif()
if (NOT CMAKE_HIP_ARCHITECTURES)
- set(CMAKE_HIP_ARCHITECTURES "gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx940;gfx941;gfx942;gfx1200;gfx1201")
+ set(CMAKE_HIP_ARCHITECTURES "gfx900;gfx908;gfx90a;gfx1030;gfx1100;gfx1101;gfx1102;gfx940;gfx941;gfx942;gfx1200;gfx1201")
endif()
file(GLOB rocm_cmake_components ${onnxruntime_ROCM_HOME}/lib/cmake/*)
-2
View File
@@ -51,8 +51,6 @@ cpu = ["onnxruntime>=1.15.0,<2"]
cuda = ["onnxruntime-gpu>=1.17.0,<2"] cuda = ["onnxruntime-gpu>=1.17.0,<2"]
openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"] openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"]
armnn = ["onnxruntime>=1.15.0,<2"] armnn = ["onnxruntime>=1.15.0,<2"]
rknn = ["onnxruntime>=1.15.0,<2", "rknn-toolkit-lite2>=2.3.0,<3"]
rocm = []
[tool.uv] [tool.uv]
compile-bytecode = true compile-bytecode = true
+4 -7
View File
@@ -2,19 +2,16 @@
echo "Initializing Immich ML $IMMICH_SOURCE_REF" echo "Initializing Immich ML $IMMICH_SOURCE_REF"
lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
# mimalloc seems to increase memory usage dramatically with openvino, need to investigate
if ! [ "$DEVICE" = "openvino" ]; then if ! [ "$DEVICE" = "openvino" ]; then
export LD_PRELOAD="$lib_path"
export LD_BIND_NOW=1
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}" : "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
else else
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=300}" : "${MACHINE_LEARNING_WORKER_TIMEOUT:=300}"
fi fi
# mimalloc seems to increase memory usage dramatically with openvino, need to investigate
if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then
lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
export LD_PRELOAD="$lib_path"
export LD_BIND_NOW=1
fi
: "${IMMICH_HOST:=[::]}" : "${IMMICH_HOST:=[::]}"
: "${IMMICH_PORT:=3003}" : "${IMMICH_PORT:=3003}"
: "${MACHINE_LEARNING_WORKERS:=1}" : "${MACHINE_LEARNING_WORKERS:=1}"
+45 -122
View File
@@ -876,7 +876,7 @@ wheels = [
[[package]] [[package]]
name = "huggingface-hub" name = "huggingface-hub"
version = "0.29.3" version = "0.29.2"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "filelock" }, { name = "filelock" },
@@ -887,9 +887,9 @@ dependencies = [
{ name = "tqdm" }, { name = "tqdm" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/e5/f9/851f34b02970e8143d41d4001b2d49e54ef113f273902103823b8bc95ada/huggingface_hub-0.29.3.tar.gz", hash = "sha256:64519a25716e0ba382ba2d3fb3ca082e7c7eb4a2fc634d200e8380006e0760e5", size = 390123 } sdist = { url = "https://files.pythonhosted.org/packages/58/b2/f8b3c9842a794e8203448725aefa02d7c9e0da42d5f22f4ed806057cc36e/huggingface_hub-0.29.2.tar.gz", hash = "sha256:590b29c0dcbd0ee4b7b023714dc1ad8563fe4a68a91463438b74e980d28afaf3", size = 389816 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/40/0c/37d380846a2e5c9a3c6a73d26ffbcfdcad5fc3eacf42fdf7cff56f2af634/huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa", size = 468997 }, { url = "https://files.pythonhosted.org/packages/13/5f/088ff08dc41808fcd99d9972b9bcfa7e3a35e30e8b0a3155b57938f1611c/huggingface_hub-0.29.2-py3-none-any.whl", hash = "sha256:c56f20fca09ef19da84dcde2b76379ecdaddf390b083f59f166715584953307d", size = 468087 },
] ]
[[package]] [[package]]
@@ -1050,7 +1050,7 @@ wheels = [
[[package]] [[package]]
name = "locust" name = "locust"
version = "2.33.2" version = "2.33.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "configargparse" }, { name = "configargparse" },
@@ -1069,9 +1069,9 @@ dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" },
{ name = "werkzeug" }, { name = "werkzeug" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/a2/9e/09ee87dc12b240248731080bfd460c7d384aadb3171f6d03a4e7314cd0e1/locust-2.33.2.tar.gz", hash = "sha256:e626ed0156f36cec94c3c6b030fc91046469e7e2f5c2e91a99aab0f28b84977e", size = 2237716 } sdist = { url = "https://files.pythonhosted.org/packages/f8/d4/873b1415c8a667982c5f229c6b74abed9fe0ead29ad87d862e5116ea2679/locust-2.33.0.tar.gz", hash = "sha256:ba291b7ab2349cc2db540adb8888bc93feb89ea4e4e10d80b935e5065091e8e9", size = 2237622 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/c7/bb55ac53173d3e92b1b2577d0f36439500406ca5be476a27b7bc01ae8a75/locust-2.33.2-py3-none-any.whl", hash = "sha256:a2f3b53dcd5ed22cecee874cd989912749663d82ec9b030637d3e43044e5878e", size = 2254591 }, { url = "https://files.pythonhosted.org/packages/1d/e5/57b58d9a545fbb2981de59ca25534d5ab4abb6742e6400bb49576ecab5ec/locust-2.33.0-py3-none-any.whl", hash = "sha256:77fcc5cc35cceee5e12d99f5bb23bc441d145bdef6967c2e93d6e4d93451553e", size = 2254520 },
] ]
[[package]] [[package]]
@@ -1109,10 +1109,6 @@ cuda = [
openvino = [ openvino = [
{ name = "onnxruntime-openvino" }, { name = "onnxruntime-openvino" },
] ]
rknn = [
{ name = "onnxruntime" },
{ name = "rknn-toolkit-lite2" },
]
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
@@ -1166,7 +1162,6 @@ requires-dist = [
{ name = "insightface", specifier = ">=0.7.3,<1.0" }, { name = "insightface", specifier = ">=0.7.3,<1.0" },
{ name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.15.0,<2" }, { name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.15.0,<2" },
{ name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.15.0,<2" }, { name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.15.0,<2" },
{ name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.15.0,<2" },
{ name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.17.0,<2", index = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/" }, { name = "onnxruntime-gpu", marker = "extra == 'cuda'", specifier = ">=1.17.0,<2", index = "https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple/" },
{ name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.17.1,<1.19.0" }, { name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.17.1,<1.19.0" },
{ name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" }, { name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" },
@@ -1176,11 +1171,10 @@ requires-dist = [
{ name = "pydantic-settings", specifier = ">=2.5.2,<3" }, { name = "pydantic-settings", specifier = ">=2.5.2,<3" },
{ name = "python-multipart", specifier = ">=0.0.6,<1.0" }, { name = "python-multipart", specifier = ">=0.0.6,<1.0" },
{ name = "rich", specifier = ">=13.4.2" }, { name = "rich", specifier = ">=13.4.2" },
{ name = "rknn-toolkit-lite2", marker = "extra == 'rknn'", specifier = ">=2.3.0,<3" },
{ name = "tokenizers", specifier = ">=0.15.0,<1.0" }, { name = "tokenizers", specifier = ">=0.15.0,<1.0" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.22.0,<1.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.22.0,<1.0" },
] ]
provides-extras = ["cpu", "cuda", "openvino", "armnn", "rknn", "rocm"] provides-extras = ["cpu", "cuda", "openvino", "armnn"]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
@@ -2137,100 +2131,29 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
] ]
[[package]]
name = "rknn-toolkit-lite2"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "psutil" },
{ name = "ruamel-yaml" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/77/6af374a4a8cd2aee762a1fb8a3050dcf3f129134bbdc4bb6bed755c4325b/rknn_toolkit_lite2-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b6733689bd09a262bcb6ba4744e690dd4b37ebeac4ed427cf45242c4b4ce9a4", size = 559372 },
{ url = "https://files.pythonhosted.org/packages/9b/0c/76ff1eb09d09ce4394a6959d2343a321d28dd9e604348ffdafceafdc344c/rknn_toolkit_lite2-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e4fefe355dc34a155680e4bcb9e4abb37ebc271f045ec9e0a4a3a018bc5beb", size = 569149 },
{ url = "https://files.pythonhosted.org/packages/0d/6e/8679562028051b02312212defc6e8c07248953f10dd7ad506e941b575bf3/rknn_toolkit_lite2-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37394371d1561f470c553f39869d7c35ff93405dffe3d0d72babf297a2b0aee9", size = 527457 },
]
[[package]]
name = "ruamel-yaml"
version = "0.18.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/46/f44d8be06b85bc7c4d8c95d658be2b68f27711f279bf9dd0612a5e4794f5/ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58", size = 143447 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/36/dfc1ebc0081e6d39924a2cc53654497f967a084a436bb64402dfce4254d9/ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1", size = 117729 },
]
[[package]]
name = "ruamel-yaml-clib"
version = "0.2.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/84/80203abff8ea4993a87d823a5f632e4d92831ef75d404c9fc78d0176d2b5/ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", size = 225315 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/57/40a958e863e299f0c74ef32a3bde9f2d1ea8d69669368c0c502a0997f57f/ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", size = 131301 },
{ url = "https://files.pythonhosted.org/packages/98/a8/29a3eb437b12b95f50a6bcc3d7d7214301c6c529d8fdc227247fa84162b5/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", size = 633728 },
{ url = "https://files.pythonhosted.org/packages/35/6d/ae05a87a3ad540259c3ad88d71275cbd1c0f2d30ae04c65dcbfb6dcd4b9f/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df", size = 722230 },
{ url = "https://files.pythonhosted.org/packages/7f/b7/20c6f3c0b656fe609675d69bc135c03aac9e3865912444be6339207b6648/ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", size = 686712 },
{ url = "https://files.pythonhosted.org/packages/cd/11/d12dbf683471f888d354dac59593873c2b45feb193c5e3e0f2ebf85e68b9/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", size = 663936 },
{ url = "https://files.pythonhosted.org/packages/72/14/4c268f5077db5c83f743ee1daeb236269fa8577133a5cfa49f8b382baf13/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", size = 696580 },
{ url = "https://files.pythonhosted.org/packages/30/fc/8cd12f189c6405a4c1cf37bd633aa740a9538c8e40497c231072d0fef5cf/ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", size = 663393 },
{ url = "https://files.pythonhosted.org/packages/80/29/c0a017b704aaf3cbf704989785cd9c5d5b8ccec2dae6ac0c53833c84e677/ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", size = 100326 },
{ url = "https://files.pythonhosted.org/packages/3a/65/fa39d74db4e2d0cd252355732d966a460a41cd01c6353b820a0952432839/ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", size = 118079 },
{ url = "https://files.pythonhosted.org/packages/fb/8f/683c6ad562f558cbc4f7c029abcd9599148c51c54b5ef0f24f2638da9fbb/ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", size = 132224 },
{ url = "https://files.pythonhosted.org/packages/3c/d2/b79b7d695e2f21da020bd44c782490578f300dd44f0a4c57a92575758a76/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", size = 641480 },
{ url = "https://files.pythonhosted.org/packages/68/6e/264c50ce2a31473a9fdbf4fa66ca9b2b17c7455b31ef585462343818bd6c/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", size = 739068 },
{ url = "https://files.pythonhosted.org/packages/86/29/88c2567bc893c84d88b4c48027367c3562ae69121d568e8a3f3a8d363f4d/ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", size = 703012 },
{ url = "https://files.pythonhosted.org/packages/11/46/879763c619b5470820f0cd6ca97d134771e502776bc2b844d2adb6e37753/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", size = 704352 },
{ url = "https://files.pythonhosted.org/packages/02/80/ece7e6034256a4186bbe50dee28cd032d816974941a6abf6a9d65e4228a7/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", size = 737344 },
{ url = "https://files.pythonhosted.org/packages/f0/ca/e4106ac7e80efbabdf4bf91d3d32fc424e41418458251712f5672eada9ce/ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", size = 714498 },
{ url = "https://files.pythonhosted.org/packages/67/58/b1f60a1d591b771298ffa0428237afb092c7f29ae23bad93420b1eb10703/ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", size = 100205 },
{ url = "https://files.pythonhosted.org/packages/b4/4f/b52f634c9548a9291a70dfce26ca7ebce388235c93588a1068028ea23fcc/ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", size = 118185 },
{ url = "https://files.pythonhosted.org/packages/48/41/e7a405afbdc26af961678474a55373e1b323605a4f5e2ddd4a80ea80f628/ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", size = 133433 },
{ url = "https://files.pythonhosted.org/packages/ec/b0/b850385604334c2ce90e3ee1013bd911aedf058a934905863a6ea95e9eb4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", size = 647362 },
{ url = "https://files.pythonhosted.org/packages/44/d0/3f68a86e006448fb6c005aee66565b9eb89014a70c491d70c08de597f8e4/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", size = 754118 },
{ url = "https://files.pythonhosted.org/packages/52/a9/d39f3c5ada0a3bb2870d7db41901125dbe2434fa4f12ca8c5b83a42d7c53/ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", size = 706497 },
{ url = "https://files.pythonhosted.org/packages/b0/fa/097e38135dadd9ac25aecf2a54be17ddf6e4c23e43d538492a90ab3d71c6/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", size = 698042 },
{ url = "https://files.pythonhosted.org/packages/ec/d5/a659ca6f503b9379b930f13bc6b130c9f176469b73b9834296822a83a132/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", size = 745831 },
{ url = "https://files.pythonhosted.org/packages/db/5d/36619b61ffa2429eeaefaab4f3374666adf36ad8ac6330d855848d7d36fd/ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", size = 715692 },
{ url = "https://files.pythonhosted.org/packages/b1/82/85cb92f15a4231c89b95dfe08b09eb6adca929ef7df7e17ab59902b6f589/ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", size = 98777 },
{ url = "https://files.pythonhosted.org/packages/d7/8f/c3654f6f1ddb75daf3922c3d8fc6005b1ab56671ad56ffb874d908bfa668/ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", size = 115523 },
{ url = "https://files.pythonhosted.org/packages/29/00/4864119668d71a5fa45678f380b5923ff410701565821925c69780356ffa/ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", size = 132011 },
{ url = "https://files.pythonhosted.org/packages/7f/5e/212f473a93ae78c669ffa0cb051e3fee1139cb2d385d2ae1653d64281507/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", size = 642488 },
{ url = "https://files.pythonhosted.org/packages/1f/8f/ecfbe2123ade605c49ef769788f79c38ddb1c8fa81e01f4dbf5cf1a44b16/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", size = 745066 },
{ url = "https://files.pythonhosted.org/packages/e2/a9/28f60726d29dfc01b8decdb385de4ced2ced9faeb37a847bd5cf26836815/ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", size = 701785 },
{ url = "https://files.pythonhosted.org/packages/84/7e/8e7ec45920daa7f76046578e4f677a3215fe8f18ee30a9cb7627a19d9b4c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", size = 693017 },
{ url = "https://files.pythonhosted.org/packages/c5/b3/d650eaade4ca225f02a648321e1ab835b9d361c60d51150bac49063b83fa/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", size = 741270 },
{ url = "https://files.pythonhosted.org/packages/87/b8/01c29b924dcbbed75cc45b30c30d565d763b9c4d540545a0eeecffb8f09c/ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", size = 709059 },
{ url = "https://files.pythonhosted.org/packages/30/8c/ed73f047a73638257aa9377ad356bea4d96125b305c34a28766f4445cc0f/ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", size = 98583 },
{ url = "https://files.pythonhosted.org/packages/b0/85/e8e751d8791564dd333d5d9a4eab0a7a115f7e349595417fd50ecae3395c/ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", size = 115190 },
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.11.0" version = "0.9.9"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } sdist = { url = "https://files.pythonhosted.org/packages/6f/c3/418441a8170e8d53d05c0b9dad69760dbc7b8a12c10dbe6db1e1205d2377/ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933", size = 3717448 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, { url = "https://files.pythonhosted.org/packages/bc/c3/2c4afa9ba467555d074b146d9aed0633a56ccdb900839fb008295d037b89/ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367", size = 10027252 },
{ url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, { url = "https://files.pythonhosted.org/packages/33/d1/439e58487cf9eac26378332e25e7d5ade4b800ce1eec7dc2cfc9b0d7ca96/ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7", size = 10840721 },
{ url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, { url = "https://files.pythonhosted.org/packages/50/44/fead822c38281ba0122f1b76b460488a175a9bd48b130650a6fb6dbcbcf9/ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d", size = 10161439 },
{ url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, { url = "https://files.pythonhosted.org/packages/11/ae/d404a2ab8e61ddf6342e09cc6b7f7846cce6b243e45c2007dbe0ca928a5d/ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a", size = 10336264 },
{ url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, { url = "https://files.pythonhosted.org/packages/6a/4e/7c268aa7d84cd709fb6f046b8972313142cffb40dfff1d2515c5e6288d54/ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe", size = 9908774 },
{ url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, { url = "https://files.pythonhosted.org/packages/cc/26/c618a878367ef1b76270fd027ca93692657d3f6122b84ba48911ef5f2edc/ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c", size = 11428127 },
{ url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, { url = "https://files.pythonhosted.org/packages/d7/9a/c5588a93d9bfed29f565baf193fe802fa676a0c837938137ea6cf0576d8c/ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be", size = 12133187 },
{ url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, { url = "https://files.pythonhosted.org/packages/3e/ff/e7980a7704a60905ed7e156a8d73f604c846d9bd87deda9cabfa6cba073a/ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590", size = 11602937 },
{ url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, { url = "https://files.pythonhosted.org/packages/24/78/3690444ad9e3cab5c11abe56554c35f005b51d1d118b429765249095269f/ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb", size = 13771698 },
{ url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, { url = "https://files.pythonhosted.org/packages/6e/bf/e477c2faf86abe3988e0b5fd22a7f3520e820b2ee335131aca2e16120038/ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0", size = 11249026 },
{ url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, { url = "https://files.pythonhosted.org/packages/f7/82/cdaffd59e5a8cb5b14c408c73d7a555a577cf6645faaf83e52fe99521715/ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17", size = 10220432 },
{ url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, { url = "https://files.pythonhosted.org/packages/fe/a4/2507d0026225efa5d4412b6e294dfe54725a78652a5c7e29e6bd0fc492f3/ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1", size = 9874602 },
{ url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, { url = "https://files.pythonhosted.org/packages/d5/be/f3aab1813846b476c4bcffe052d232244979c3cd99d751c17afb530ca8e4/ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57", size = 10851212 },
{ url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, { url = "https://files.pythonhosted.org/packages/8b/45/8e5fd559bea0d2f57c4e12bf197a2fade2fac465aa518284f157dfbca92b/ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e", size = 11327490 },
{ url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, { url = "https://files.pythonhosted.org/packages/42/55/e6c90f13880aeef327746052907e7e930681f26a164fe130ddac28b08269/ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1", size = 10227912 },
{ url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, { url = "https://files.pythonhosted.org/packages/35/b2/da925693cb82a1208aa34966c0f36cb222baca94e729dd22a587bc22d0f3/ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1", size = 11355632 },
{ url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, { url = "https://files.pythonhosted.org/packages/31/d8/de873d1c1b020d668d8ec9855d390764cb90cf8f6486c0983da52be8b7b7/ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf", size = 10435860 },
] ]
[[package]] [[package]]
@@ -2398,27 +2321,27 @@ wheels = [
[[package]] [[package]]
name = "tokenizers" name = "tokenizers"
version = "0.21.1" version = "0.21.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "huggingface-hub" }, { name = "huggingface-hub" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/92/76/5ac0c97f1117b91b7eb7323dcd61af80d72f790b4df71249a7850c195f30/tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab", size = 343256 } sdist = { url = "https://files.pythonhosted.org/packages/20/41/c2be10975ca37f6ec40d7abd7e98a5213bb04f284b869c1a24e6504fd94d/tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4", size = 343021 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a5/1f/328aee25f9115bf04262e8b4e5a2050b7b7cf44b59c74e982db7270c7f30/tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41", size = 2780767 }, { url = "https://files.pythonhosted.org/packages/b0/5c/8b09607b37e996dc47e70d6a7b6f4bdd4e4d5ab22fe49d7374565c7fefaf/tokenizers-0.21.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3c4c93eae637e7d2aaae3d376f06085164e1660f89304c0ab2b1d08a406636b2", size = 2647461 },
{ url = "https://files.pythonhosted.org/packages/ae/1a/4526797f3719b0287853f12c5ad563a9be09d446c44ac784cdd7c50f76ab/tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3", size = 2650555 }, { url = "https://files.pythonhosted.org/packages/22/7a/88e58bb297c22633ed1c9d16029316e5b5ac5ee44012164c2edede599a5e/tokenizers-0.21.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:f53ea537c925422a2e0e92a24cce96f6bc5046bbef24a1652a5edc8ba975f62e", size = 2563639 },
{ url = "https://files.pythonhosted.org/packages/4d/7a/a209b29f971a9fdc1da86f917fe4524564924db50d13f0724feed37b2a4d/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f", size = 2937541 }, { url = "https://files.pythonhosted.org/packages/f7/14/83429177c19364df27d22bc096d4c2e431e0ba43e56c525434f1f9b0fd00/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b177fb54c4702ef611de0c069d9169f0004233890e0c4c5bd5508ae05abf193", size = 2903304 },
{ url = "https://files.pythonhosted.org/packages/3c/1e/b788b50ffc6191e0b1fc2b0d49df8cff16fe415302e5ceb89f619d12c5bc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf", size = 2819058 }, { url = "https://files.pythonhosted.org/packages/7e/db/3433eab42347e0dc5452d8fcc8da03f638c9accffefe5a7c78146666964a/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b43779a269f4629bebb114e19c3fca0223296ae9fea8bb9a7a6c6fb0657ff8e", size = 2804378 },
{ url = "https://files.pythonhosted.org/packages/36/aa/3626dfa09a0ecc5b57a8c58eeaeb7dd7ca9a37ad9dd681edab5acd55764c/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8", size = 3133278 }, { url = "https://files.pythonhosted.org/packages/57/8b/7da5e6f89736c2ade02816b4733983fca1c226b0c42980b1ae9dc8fcf5cc/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aeb255802be90acfd363626753fda0064a8df06031012fe7d52fd9a905eb00e", size = 3095488 },
{ url = "https://files.pythonhosted.org/packages/a4/4d/8fbc203838b3d26269f944a89459d94c858f5b3f9a9b6ee9728cdcf69161/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0", size = 3144253 }, { url = "https://files.pythonhosted.org/packages/4d/f6/5ed6711093dc2c04a4e03f6461798b12669bc5a17c8be7cce1240e0b5ce8/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b09dbeb7a8d73ee204a70f94fc06ea0f17dcf0844f16102b9f414f0b7463ba", size = 3121410 },
{ url = "https://files.pythonhosted.org/packages/d8/1b/2bd062adeb7c7511b847b32e356024980c0ffcf35f28947792c2d8ad2288/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c", size = 3398225 }, { url = "https://files.pythonhosted.org/packages/81/42/07600892d48950c5e80505b81411044a2d969368cdc0d929b1c847bf6697/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:400832c0904f77ce87c40f1a8a27493071282f785724ae62144324f171377273", size = 3388821 },
{ url = "https://files.pythonhosted.org/packages/8a/63/38be071b0c8e06840bc6046991636bcb30c27f6bb1e670f4f4bc87cf49cc/tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a", size = 3038874 }, { url = "https://files.pythonhosted.org/packages/22/06/69d7ce374747edaf1695a4f61b83570d91cc8bbfc51ccfecf76f56ab4aac/tokenizers-0.21.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84ca973b3a96894d1707e189c14a774b701596d579ffc7e69debfc036a61a04", size = 3008868 },
{ url = "https://files.pythonhosted.org/packages/ec/83/afa94193c09246417c23a3c75a8a0a96bf44ab5630a3015538d0c316dd4b/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf", size = 9014448 }, { url = "https://files.pythonhosted.org/packages/c8/69/54a0aee4d576045b49a0eb8bffdc495634309c823bf886042e6f46b80058/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb7202d231b273c34ec67767378cd04c767e967fda12d4a9e36208a34e2f137e", size = 8975831 },
{ url = "https://files.pythonhosted.org/packages/ae/b3/0e1a37d4f84c0f014d43701c11eb8072704f6efe8d8fc2dcdb79c47d76de/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6", size = 8937877 }, { url = "https://files.pythonhosted.org/packages/f7/f3/b776061e4f3ebf2905ba1a25d90380aafd10c02d406437a8ba22d1724d76/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:089d56db6782a73a27fd8abf3ba21779f5b85d4a9f35e3b493c7bbcbbf0d539b", size = 8920746 },
{ url = "https://files.pythonhosted.org/packages/ac/33/ff08f50e6d615eb180a4a328c65907feb6ded0b8f990ec923969759dc379/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d", size = 9186645 }, { url = "https://files.pythonhosted.org/packages/d8/ee/ce83d5ec8b6844ad4c3ecfe3333d58ecc1adc61f0878b323a15355bcab24/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:c87ca3dc48b9b1222d984b6b7490355a6fdb411a2d810f6f05977258400ddb74", size = 9161814 },
{ url = "https://files.pythonhosted.org/packages/5f/aa/8ae85f69a9f6012c6f8011c6f4aa1c96154c816e9eea2e1b758601157833/tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f", size = 9384380 }, { url = "https://files.pythonhosted.org/packages/18/07/3e88e65c0ed28fa93aa0c4d264988428eef3df2764c3126dc83e243cb36f/tokenizers-0.21.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4145505a973116f91bc3ac45988a92e618a6f83eb458f49ea0790df94ee243ff", size = 9357138 },
{ url = "https://files.pythonhosted.org/packages/e8/5b/a5d98c89f747455e8b7a9504910c865d5e51da55e825a7ae641fb5ff0a58/tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3", size = 2239506 }, { url = "https://files.pythonhosted.org/packages/15/b0/dc4572ca61555fc482ebc933f26cb407c6aceb3dc19c301c68184f8cad03/tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a", size = 2202266 },
{ url = "https://files.pythonhosted.org/packages/e6/b6/072a8e053ae600dcc2ac0da81a23548e3b523301a442a6ca900e92ac35be/tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382", size = 2435481 }, { url = "https://files.pythonhosted.org/packages/44/69/d21eb253fa91622da25585d362a874fa4710be600f0ea9446d8d0217cec1/tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c", size = 2389192 },
] ]
[[package]] [[package]]
@@ -2465,14 +2388,14 @@ wheels = [
[[package]] [[package]]
name = "types-setuptools" name = "types-setuptools"
version = "76.0.0.20250313" version = "75.8.2.20250305"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "setuptools" }, { name = "setuptools" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b8/0f/2d1d000c2be3919bcdea15e5da48456bf1e55c18d02c5509ea59dade1408/types_setuptools-76.0.0.20250313.tar.gz", hash = "sha256:b2be66f550f95f3cad2a7d46177b273c7e9c80df7d257fa57addbbcfc8126a9e", size = 43627 } sdist = { url = "https://files.pythonhosted.org/packages/4f/18/a996861f5225e7d533a8d8b6aa61bcc9183429a6b8bc93b850aa2e22974d/types_setuptools-75.8.2.20250305.tar.gz", hash = "sha256:a987269b49488f21961a1d99aa8d281b611625883def6392a93855b31544e405", size = 42609 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/89/ea9669a0a76b160ffb312d0b02b15bad053c1bc81d2a54e42e3a402ca754/types_setuptools-76.0.0.20250313-py3-none-any.whl", hash = "sha256:bf454b2a49b8cfd7ebcf5844d4dd5fe4c8666782df1e3663c5866fd51a47460e", size = 65845 }, { url = "https://files.pythonhosted.org/packages/d9/5b/bb33f99239a6d54ed1d8220a088d96d2ccacac7abb317df0d68d2500f3be/types_setuptools-75.8.2.20250305-py3-none-any.whl", hash = "sha256:ba80953fd1f5f49e552285c024f75b5223096a38a5138a54d18ddd3fa8f6a2d4", size = 63727 },
] ]
[[package]] [[package]]
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"flutter": "3.29.2" "flutter": "3.29.1"
} }
@@ -1,10 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:tools="http://schemas.android.com/tools">
<!-- Flutter needs it to communicate with the running application <!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
</manifest>
<application android:label="Immich-Debug" tools:replace="android:label">
</application>
</manifest>
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 188, "android.injected.version.code" => 187,
"android.injected.version.name" => "1.130.0", "android.injected.version.name" => "1.129.0",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت", "control_bottom_app_bar_edit_time": "تحرير التاريخ والوقت",
"control_bottom_app_bar_favorite": "مفضل", "control_bottom_app_bar_favorite": "مفضل",
"control_bottom_app_bar_share": "يشارك", "control_bottom_app_bar_share": "يشارك",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "مشاركة إلى", "control_bottom_app_bar_share_to": "مشاركة إلى",
"control_bottom_app_bar_stack": "تجميع", "control_bottom_app_bar_stack": "تجميع",
"control_bottom_app_bar_trash_from_immich": "حذفه ونقله في سله المهملات", "control_bottom_app_bar_trash_from_immich": "حذفه ونقله في سله المهملات",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "وحدة زمنية", "edit_date_time_dialog_timezone": "وحدة زمنية",
"edit_image_title": "Edit", "edit_image_title": "Edit",
"edit_location_dialog_title": "موقع", "edit_location_dialog_title": "موقع",
"empty_folder": "This folder is empty",
"end_date": "End date", "end_date": "End date",
"enqueued": "Enqueued", "enqueued": "Enqueued",
"enter_wifi_name": "Enter WiFi name", "enter_wifi_name": "Enter WiFi name",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "إضافة موقع", "exif_bottom_sheet_location_add": "إضافة موقع",
"exif_bottom_sheet_people": "الناس", "exif_bottom_sheet_people": "الناس",
"exif_bottom_sheet_person_add_person": "اضف اسما", "exif_bottom_sheet_person_add_person": "اضف اسما",
"exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "أعمال جارية", "experimental_settings_new_asset_list_subtitle": "أعمال جارية",
"experimental_settings_new_asset_list_title": "تمكين شبكة الصور التجريبية", "experimental_settings_new_asset_list_title": "تمكين شبكة الصور التجريبية",
"experimental_settings_subtitle": "استخدام على مسؤوليتك الخاصة!", "experimental_settings_subtitle": "استخدام على مسؤوليتك الخاصة!",
@@ -276,16 +270,11 @@
"external_network": "External network", "external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed", "failed": "Failed",
"failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder",
"favorites": "Favorites", "favorites": "Favorites",
"favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة", "favorites_page_no_favorites": "لم يتم العثور على الأصول المفضلة",
"favorites_page_title": "المفضلة", "favorites_page_title": "المفضلة",
"filename_search": "File name or extension", "filename_search": "File name or extension",
"filter": "Filter", "filter": "Filter",
"folder": "Folder",
"folder_not_found": "Folder not found",
"folders": "Folders",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission", "grant_permission": "Grant permission",
"haptic_feedback_switch": "تمكين ردود الفعل اللمسية", "haptic_feedback_switch": "تمكين ردود الفعل اللمسية",
+307 -318
View File
@@ -8,26 +8,26 @@
"action_common_update": "Actualitzar", "action_common_update": "Actualitzar",
"add_a_name": "Afegeix un nom", "add_a_name": "Afegeix un nom",
"add_endpoint": "afegir endpoint", "add_endpoint": "afegir endpoint",
"add_to_album_bottom_sheet_added": "Afegit a {album}", "add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Ja està a {album}", "add_to_album_bottom_sheet_already_exists": "Already in {album}",
"advanced_settings_log_level_title": "Nivell de registre: {}", "advanced_settings_log_level_title": "Log level: {}",
"advanced_settings_prefer_remote_subtitle": "Alguns dispositius són molt lents en carregar miniatures dels elements del dispositiu. Activeu aquest paràmetre per carregar imatges remotes en el seu lloc.", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from assets on the device. Activate this setting to load remote images instead.",
"advanced_settings_prefer_remote_title": "Prefereix imatges remotes", "advanced_settings_prefer_remote_title": "Prefereix imatges remotes",
"advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa", "advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol·licitud de xarxa",
"advanced_settings_proxy_headers_title": "Capçaleres de proxy", "advanced_settings_proxy_headers_title": "Capçaleres de proxy",
"advanced_settings_self_signed_ssl_subtitle": "Omet la verificació del certificat SSL del servidor. Requerit per a certificats autosignats.", "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.",
"advanced_settings_self_signed_ssl_title": "Permet certificats SSL autosignats", "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates",
"advanced_settings_tile_subtitle": "Configuració avançada de l'usuari", "advanced_settings_tile_subtitle": "Advanced user's settings",
"advanced_settings_tile_title": "Avançat", "advanced_settings_tile_title": "Avançat",
"advanced_settings_troubleshooting_subtitle": "Habilita funcions addicionals per a la resolució de problemes", "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting",
"advanced_settings_troubleshooting_title": "Resolució de problemes", "advanced_settings_troubleshooting_title": "Resolució de problemes",
"album_info_card_backup_album_excluded": "Exclosos", "album_info_card_backup_album_excluded": "Exclosos",
"album_info_card_backup_album_included": "Inclosos", "album_info_card_backup_album_included": "Inclosos",
"albums": "Àlbums", "albums": "Àlbums",
"album_thumbnail_card_item": "1 element", "album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} elements", "album_thumbnail_card_items": "{} items",
"album_thumbnail_card_shared": " · Compartit", "album_thumbnail_card_shared": " · Shared",
"album_thumbnail_owned": "Propi", "album_thumbnail_owned": "Owned",
"album_thumbnail_shared_by": "Compartit per {}", "album_thumbnail_shared_by": "Compartit per {}",
"album_viewer_appbar_delete_confirm": "Confirmes que vols suprimir aquest àlbum del teu compte?", "album_viewer_appbar_delete_confirm": "Confirmes que vols suprimir aquest àlbum del teu compte?",
"album_viewer_appbar_share_delete": "Esborra l'àlbum", "album_viewer_appbar_share_delete": "Esborra l'àlbum",
@@ -37,28 +37,28 @@
"album_viewer_appbar_share_err_title": "Error al modificar el títol de l'àlbum", "album_viewer_appbar_share_err_title": "Error al modificar el títol de l'àlbum",
"album_viewer_appbar_share_leave": "Surt de l'àlbum", "album_viewer_appbar_share_leave": "Surt de l'àlbum",
"album_viewer_appbar_share_remove": "Treu de l'àlbum", "album_viewer_appbar_share_remove": "Treu de l'àlbum",
"album_viewer_appbar_share_to": "Comparteix amb", "album_viewer_appbar_share_to": "Share To",
"album_viewer_page_share_add_users": "Afegeix usuaris", "album_viewer_page_share_add_users": "Afegeix usuaris",
"all": "Tot", "all": "Tot",
"all_people_page_title": "Persones", "all_people_page_title": "Persones",
"all_videos_page_title": "Vídeos", "all_videos_page_title": "Vídeos",
"app_bar_signout_dialog_content": "Estàs segur que vols tancar la sessió?", "app_bar_signout_dialog_content": "Are you sure you want to sign out?",
"app_bar_signout_dialog_ok": "", "app_bar_signout_dialog_ok": "Yes",
"app_bar_signout_dialog_title": "Tanca la sessió", "app_bar_signout_dialog_title": "Sign out",
"archived": "Arxivat", "archived": "Arxivat",
"archive_page_no_archived_assets": "No s'ha trobat res arxivat", "archive_page_no_archived_assets": "No s'ha trobat res arxivat",
"archive_page_title": "Arxiu({})", "archive_page_title": "Arxiu({})",
"asset_action_delete_err_read_only": "No es poden esborrar el fitxer(s) de només lectura, ometent", "asset_action_delete_err_read_only": "No es poden esborrar el fitxer(s) de només lectura, ometent",
"asset_action_share_err_offline": "No s'ha pogut obtenir el fitxer(s) sense connexió, ometent", "asset_action_share_err_offline": "No s'ha pogut obtenir el fitxer(s) sense connexió, ometent",
"asset_list_group_by_sub_title": "Agrupar per", "asset_list_group_by_sub_title": "Agrupar per",
"asset_list_layout_settings_dynamic_layout_title": "Disseny dinàmic", "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_automatically": "Automàtic", "asset_list_layout_settings_group_automatically": "Automàtic",
"asset_list_layout_settings_group_by": "Agrupa elements per", "asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Mes", "asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Mes + dia", "asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_sub_title": "Disseny", "asset_list_layout_sub_title": "Disseny",
"asset_list_settings_subtitle": "Configuració del disseny de la graella de fotos", "asset_list_settings_subtitle": "Photo grid layout settings",
"asset_list_settings_title": "Graella de fotos", "asset_list_settings_title": "Photo Grid",
"asset_restored_successfully": "Element recuperat correctament", "asset_restored_successfully": "Element recuperat correctament",
"assets_deleted_permanently": "{} element(s) esborrats permanentment", "assets_deleted_permanently": "{} element(s) esborrats permanentment",
"assets_deleted_permanently_from_server": "{} element(s) esborrats permanentment del servidor d'Immich", "assets_deleted_permanently_from_server": "{} element(s) esborrats permanentment del servidor d'Immich",
@@ -73,97 +73,97 @@
"background_location_permission": "Permís d'ubicació en segon pla", "background_location_permission": "Permís d'ubicació en segon pla",
"background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accés a la ubicació precisa perquè l'aplicació pugui llegir el nom de la xarxa Wi-Fi", "background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accés a la ubicació precisa perquè l'aplicació pugui llegir el nom de la xarxa Wi-Fi",
"backup_album_selection_page_albums_device": "Àlbums al dispositiu ({})", "backup_album_selection_page_albums_device": "Àlbums al dispositiu ({})",
"backup_album_selection_page_albums_tap": "Un toc per incloure, doble toc per excloure", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
"backup_album_selection_page_assets_scatter": "Els elements poden dispersar-se en diversos àlbums. Per tant, els àlbums es poden incloure o excloure durant el procés de còpia de seguretat.", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
"backup_album_selection_page_select_albums": "Selecciona àlbums", "backup_album_selection_page_select_albums": "Selecciona àlbums",
"backup_album_selection_page_selection_info": "Informació de la selecció", "backup_album_selection_page_selection_info": "Informació de la selecció",
"backup_album_selection_page_total_assets": "Total d'elements únics", "backup_album_selection_page_total_assets": "Total d'elements únics",
"backup_all": "Tots", "backup_all": "All",
"backup_background_service_backup_failed_message": "No s'ha pogut copiar els elements. Tornant a intentar…", "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
"backup_background_service_connection_failed_message": "No s'ha pogut connectar al servidor. Tornant a intentar…", "backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
"backup_background_service_current_upload_notification": "Pujant {}", "backup_background_service_current_upload_notification": "Pujant {}",
"backup_background_service_default_notification": "Cercant nous elements...", "backup_background_service_default_notification": "Cercant nous elements...",
"backup_background_service_error_title": "Error copiant", "backup_background_service_error_title": "Error copiant",
"backup_background_service_in_progress_notification": "Copiant els teus elements", "backup_background_service_in_progress_notification": "Copiant els teus elements",
"backup_background_service_upload_failure_notification": "Error al pujar {}", "backup_background_service_upload_failure_notification": "Error al pujar {}",
"backup_controller_page_albums": "Copia els àlbums", "backup_controller_page_albums": "Copia els àlbums",
"backup_controller_page_background_app_refresh_disabled_content": "Activa l'actualització en segon pla de l'aplicació a Configuració > General > Actualització en segon pla per utilitzar la copia de seguretat en segon pla.", "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Actualització en segon pla desactivada", "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Vés a configuració", "backup_controller_page_background_app_refresh_enable_button_text": "Vés a configuració",
"backup_controller_page_background_battery_info_link": "Mostra'm com", "backup_controller_page_background_battery_info_link": "Mostra'm com",
"backup_controller_page_background_battery_info_message": "Per obtenir la millor experiència de copia de seguretat en segon pla, desactiveu qualsevol optimització de bateria que restringeixi l'activitat en segon pla per a Immich.\n\nAtès que això és específic del dispositiu, busqueu la informació necessària per al fabricant del vostre dispositiu", "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
"backup_controller_page_background_battery_info_ok": "D'acord", "backup_controller_page_background_battery_info_ok": "D'acord",
"backup_controller_page_background_battery_info_title": "Optimitzacions de bateria", "backup_controller_page_background_battery_info_title": "Optimitzacions de bateria",
"backup_controller_page_background_charging": "Només mentre es carrega", "backup_controller_page_background_charging": "Només mentre es carrega",
"backup_controller_page_background_configure_error": "No s'ha pogut configurar el servei en segon pla", "backup_controller_page_background_configure_error": "Failed to configure the background service",
"backup_controller_page_background_delay": "Retard en la copia de seguretat de nous elements: {}", "backup_controller_page_background_delay": "Delay new assets backup: {}",
"backup_controller_page_background_description": "Activeu el servei en segon pla per copiar automàticament tots els nous elements sense haver d'obrir l'aplicació.", "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
"backup_controller_page_background_is_off": "La còpia automàtica en segon pla està desactivada", "backup_controller_page_background_is_off": "Automatic background backup is off",
"backup_controller_page_background_is_on": "La còpia automàtica en segon pla està activada", "backup_controller_page_background_is_on": "Automatic background backup is on",
"backup_controller_page_background_turn_off": "Desactiva el servei en segon pla", "backup_controller_page_background_turn_off": "Turn off background service",
"backup_controller_page_background_turn_on": "Activa el servei en segon pla", "backup_controller_page_background_turn_on": "Turn on background service",
"backup_controller_page_background_wifi": "Només amb WiFi", "backup_controller_page_background_wifi": "Only on WiFi",
"backup_controller_page_backup": "Còpia", "backup_controller_page_backup": "Còpia",
"backup_controller_page_backup_selected": "Seleccionat: ", "backup_controller_page_backup_selected": "Selected: ",
"backup_controller_page_backup_sub": "Fotografies i vídeos copiats", "backup_controller_page_backup_sub": "Fotografies i vídeos copiats",
"backup_controller_page_cancel": "Cancel·la", "backup_controller_page_cancel": "Cancel·la",
"backup_controller_page_created": "Creat el: {}", "backup_controller_page_created": "Created on: {}",
"backup_controller_page_desc_backup": "Activeu la còpia de seguretat per pujar automàticament els nous elements al servidor en obrir l'aplicació.", "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.",
"backup_controller_page_excluded": "Exclosos:", "backup_controller_page_excluded": "Exclosos:",
"backup_controller_page_failed": "Fallats ({})", "backup_controller_page_failed": "Failed ({})",
"backup_controller_page_filename": "Nom de l'arxiu: {} [{}]", "backup_controller_page_filename": "File name: {} [{}]",
"backup_controller_page_id": "ID: {}", "backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Informació de la còpia", "backup_controller_page_info": "Informació de la còpia",
"backup_controller_page_none_selected": "Cap seleccionat", "backup_controller_page_none_selected": "None selected",
"backup_controller_page_remainder": "Restant", "backup_controller_page_remainder": "Restant",
"backup_controller_page_remainder_sub": "Fotografies i vídeos restants per copiar de la selecció", "backup_controller_page_remainder_sub": "Fotografies i vídeos restants per copiar de la selecció",
"backup_controller_page_select": "Selecciona", "backup_controller_page_select": "Selecciona",
"backup_controller_page_server_storage": "Emmagatzematge del servidor", "backup_controller_page_server_storage": "Server Storage",
"backup_controller_page_start_backup": "Inicia la còpia", "backup_controller_page_start_backup": "Inicia la còpia",
"backup_controller_page_status_off": "La copia de seguretat està desactivada", "backup_controller_page_status_off": "Automatic foreground backup is off",
"backup_controller_page_status_on": "La copia de seguretat està activada", "backup_controller_page_status_on": "Automatic foreground backup is on",
"backup_controller_page_storage_format": "{} de {} utilitzats", "backup_controller_page_storage_format": "{} of {} used",
"backup_controller_page_to_backup": "Àlbums a copiar", "backup_controller_page_to_backup": "Àlbums a copiar",
"backup_controller_page_total": "Total", "backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "Totes les fotografies i vídeos dels àlbums seleccionats", "backup_controller_page_total_sub": "Totes les fotografies i vídeos dels àlbums seleccionats",
"backup_controller_page_turn_off": "Desactiva la còpia de seguretat", "backup_controller_page_turn_off": "Turn off foreground backup",
"backup_controller_page_turn_on": "Activa la còpia de seguretat", "backup_controller_page_turn_on": "Turn on foreground backup",
"backup_controller_page_uploading_file_info": "S'està pujant la informació del fitxer", "backup_controller_page_uploading_file_info": "Uploading file info",
"backup_err_only_album": "No es pot eliminar l'únic àlbum", "backup_err_only_album": "Cannot remove the only album",
"backup_info_card_assets": "elements", "backup_info_card_assets": "elements",
"backup_manual_cancelled": "Cancel·lat", "backup_manual_cancelled": "Cancelled",
"backup_manual_failed": "Fallat", "backup_manual_failed": "Failed",
"backup_manual_in_progress": "La pujada ja està en curs. Torneu-ho a provar més tard", "backup_manual_in_progress": "Upload already in progress. Try after sometime",
"backup_manual_success": "Èxit", "backup_manual_success": "Success",
"backup_manual_title": "Estat de pujada", "backup_manual_title": "Upload status",
"backup_options_page_title": "Opcions de còpia de seguretat", "backup_options_page_title": "Opcions de còpia de seguretat",
"backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla", "backup_setting_subtitle": "Gestiona la configuració de càrrega en segon pla i en primer pla",
"cache_settings_album_thumbnails": "Miniatures de la pàgina de la biblioteca ({} elements)", "cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
"cache_settings_clear_cache_button": "Neteja la memòria cau", "cache_settings_clear_cache_button": "Clear cache",
"cache_settings_clear_cache_button_title": "Neteja la memòria cau de l'aplicació. Això impactarà significativament el rendiment fins que la memòria cau es torni a reconstruir.", "cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
"cache_settings_duplicated_assets_clear_button": "NETEJA", "cache_settings_duplicated_assets_clear_button": "CLEAR",
"cache_settings_duplicated_assets_subtitle": "Fotos i vídeos que estan a la llista negra de l'aplicació.", "cache_settings_duplicated_assets_subtitle": "Photos and videos that are black listed by the app",
"cache_settings_duplicated_assets_title": "Elements duplicats ({})", "cache_settings_duplicated_assets_title": "Duplicated Assets ({})",
"cache_settings_image_cache_size": "Mida de la memòria cau de imatges ({} elements)", "cache_settings_image_cache_size": "Image cache size ({} assets)",
"cache_settings_statistics_album": "Miniatures de la biblioteca", "cache_settings_statistics_album": "Library thumbnails",
"cache_settings_statistics_assets": "{} elements ({})", "cache_settings_statistics_assets": "{} elements ({})",
"cache_settings_statistics_full": "Imatges completes", "cache_settings_statistics_full": "Imatges completes",
"cache_settings_statistics_shared": "Miniatures d'àlbums compartits", "cache_settings_statistics_shared": "Shared album thumbnails",
"cache_settings_statistics_thumbnail": "Miniatures", "cache_settings_statistics_thumbnail": "Miniatures",
"cache_settings_statistics_title": "Ús de memòria cau", "cache_settings_statistics_title": "Ús de memòria cau",
"cache_settings_subtitle": "Controla el comportament de la memòria cau de l'aplicació mòbil Immich", "cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
"cache_settings_thumbnail_size": "Mida de la memòria cau de les miniatures ({} elements)", "cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
"cache_settings_tile_subtitle": "Controla el comportament de l'emmagatzematge local", "cache_settings_tile_subtitle": "Control the local storage behaviour",
"cache_settings_tile_title": "Emmagatzematge local", "cache_settings_tile_title": "Local Storage",
"cache_settings_title": "Configuració de la memòria cau", "cache_settings_title": "Configuració de la memòria cau",
"cancel": "Cancel·la", "cancel": "Cancel·la",
"canceled": "Cancel·lat", "canceled": "Cancel·lat",
"change_display_order": "Canvia l'ordre de visualització", "change_display_order": "Canvia l'ordre de visualització",
"change_password_form_confirm_password": "Confirma la contrasenya", "change_password_form_confirm_password": "Confirma la contrasenya",
"change_password_form_description": "Hola {name},\n\nAquesta és la primera vegada que inicies sessió al sistema o bé s'ha sol·licitat canviar la teva contrasenya. Si us plau, introdueix la nova contrasenya a continuació.", "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "Nova contrasenya", "change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Les contrasenyes no coincideixen", "change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Torna a introduir la nova contrasenya", "change_password_form_reenter_new_password": "Re-enter New Password",
"check_corrupt_asset_backup": "Comprovar les còpies de seguretat corruptes", "check_corrupt_asset_backup": "Comprovar les còpies de seguretat corruptes",
"check_corrupt_asset_backup_button": "Realitzar comprovació", "check_corrupt_asset_backup_button": "Realitzar comprovació",
"check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.", "check_corrupt_asset_backup_description": "Executeu aquesta comprovació només mitjançant Wi-Fi i un cop s'hagi fet una còpia de seguretat de tots els actius. El procediment pot trigar uns minuts.",
@@ -176,14 +176,14 @@
"client_cert_remove_msg": "S'ha eliminat el certificat del client", "client_cert_remove_msg": "S'ha eliminat el certificat del client",
"client_cert_subtitle": "Només admet el format PKCS12 (.p12, .pfx). La importació/eliminació de certificats només està disponible abans d'iniciar sessió", "client_cert_subtitle": "Només admet el format PKCS12 (.p12, .pfx). La importació/eliminació de certificats només està disponible abans d'iniciar sessió",
"client_cert_title": "Certificat de client SSL", "client_cert_title": "Certificat de client SSL",
"common_add_to_album": "Afegir a l'àlbum", "common_add_to_album": "Add to album",
"common_change_password": "Canvia la contrasenya", "common_change_password": "Change Password",
"common_create_new_album": "Crea un àlbum nou", "common_create_new_album": "Crea un àlbum nou",
"common_server_error": "Si us plau, comproveu la vostra connexió de xarxa, assegureu-vos que el servidor és accessible i que les versions de l'aplicació i del servidor són compatibles.", "common_server_error": "Please check your network connection, make sure the server is reachable and app/server versions are compatible.",
"common_shared": "Compartit", "common_shared": "Compartit",
"completed": "Completat", "completed": "Completat",
"contextual_search": "Sortida del sol a la platja", "contextual_search": "Sortida del sol a la platja",
"control_bottom_app_bar_add_to_album": "Afegir a l'àlbum", "control_bottom_app_bar_add_to_album": "Add to album",
"control_bottom_app_bar_album_info": "{} elements", "control_bottom_app_bar_album_info": "{} elements",
"control_bottom_app_bar_album_info_shared": "{} elements - Compartits", "control_bottom_app_bar_album_info_shared": "{} elements - Compartits",
"control_bottom_app_bar_archive": "Arxiu", "control_bottom_app_bar_archive": "Arxiu",
@@ -193,32 +193,31 @@
"control_bottom_app_bar_delete_from_local": "Suprimeix del dispositiu", "control_bottom_app_bar_delete_from_local": "Suprimeix del dispositiu",
"control_bottom_app_bar_download": "Descarrega", "control_bottom_app_bar_download": "Descarrega",
"control_bottom_app_bar_edit": "Edita", "control_bottom_app_bar_edit": "Edita",
"control_bottom_app_bar_edit_location": "Edita la ubicació", "control_bottom_app_bar_edit_location": "Edit Location",
"control_bottom_app_bar_edit_time": "Edita data i hora", "control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Preferit", "control_bottom_app_bar_favorite": "Preferit",
"control_bottom_app_bar_share": "Comparteix", "control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_link": "Comparteix l'enllaç", "control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_share_to": "Comparteix a", "control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_stack": "Apilar",
"control_bottom_app_bar_trash_from_immich": "Mou a paperera", "control_bottom_app_bar_trash_from_immich": "Mou a paperera",
"control_bottom_app_bar_unarchive": "Desarxiva", "control_bottom_app_bar_unarchive": "Desarxiva",
"control_bottom_app_bar_unfavorite": "No preferit", "control_bottom_app_bar_unfavorite": "Unfavorite",
"control_bottom_app_bar_upload": "Puja", "control_bottom_app_bar_upload": "Upload",
"create_album": "Crear àlbum", "create_album": "Crear àlbum",
"create_album_page_untitled": "Sense títol", "create_album_page_untitled": "Untitled",
"create_new": "CREAR NOU", "create_new": "CREAR NOU",
"create_shared_album_page_create": "Crea", "create_shared_album_page_create": "Create",
"create_shared_album_page_share": "Comparteix", "create_shared_album_page_share": "Comparteix",
"create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS",
"create_shared_album_page_share_select_photos": "Escull fotografies", "create_shared_album_page_share_select_photos": "Escull fotografies",
"crop": "Retalla", "crop": "Retalla",
"curated_location_page_title": "Localitzacions", "curated_location_page_title": "Localitzacions",
"curated_object_page_title": "Coses", "curated_object_page_title": "Coses",
"current_server_address": "Adreça actual del servidor", "current_server_address": "Current server address",
"daily_title_text_date": "E, dd MMM", "daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, dd MMM, yyyy", "daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, d LLL, y • hh:mm", "date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "Aquests elements seran eliminats de manera permanent d'Immich i del vostre dispositiu.", "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device",
"delete_dialog_alert_local": "Aquests elements s'eliminaran permanentment del vostre dispositiu, però encara estaran disponibles al servidor Immich", "delete_dialog_alert_local": "Aquests elements s'eliminaran permanentment del vostre dispositiu, però encara estaran disponibles al servidor Immich",
"delete_dialog_alert_local_non_backed_up": "Alguns dels elements no tenen còpia de seguretat a Immich i s'eliminaran permanentment del dispositiu", "delete_dialog_alert_local_non_backed_up": "Alguns dels elements no tenen còpia de seguretat a Immich i s'eliminaran permanentment del dispositiu",
"delete_dialog_alert_remote": "Aquests elements s'eliminaran permanentment del servidor Immich", "delete_dialog_alert_remote": "Aquests elements s'eliminaran permanentment del servidor Immich",
@@ -228,11 +227,11 @@
"delete_dialog_title": "Esborra permanentment", "delete_dialog_title": "Esborra permanentment",
"delete_local_dialog_ok_backed_up_only": "Esborrar només les que tinguin còpia de seguretat", "delete_local_dialog_ok_backed_up_only": "Esborrar només les que tinguin còpia de seguretat",
"delete_local_dialog_ok_force": "Suprimeix de totes maneres", "delete_local_dialog_ok_force": "Suprimeix de totes maneres",
"delete_shared_link_dialog_content": "Esteu segurs que voleu eliminar aquest enllaç compartit?", "delete_shared_link_dialog_content": "Are you sure you want to delete this shared link?",
"delete_shared_link_dialog_title": "Suprimeix l'enllaç compartit", "delete_shared_link_dialog_title": "Delete Shared Link",
"description_input_hint_text": "Afegeix descripció...", "description_input_hint_text": "Afegeix descripció...",
"description_input_submit_error": "S'ha produït un error en actualitzar la descripció, comproveu el registre per a més detalls", "description_input_submit_error": "Error updating description, check the log for more details",
"description_search": "Jornada de senderisme al Pedraforca", "description_search": "Jornada de senderisme a Sapa",
"download_canceled": "Descàrrega cancel·lada", "download_canceled": "Descàrrega cancel·lada",
"download_complete": "Descàrrega completada", "download_complete": "Descàrrega completada",
"download_enqueue": "Descàrrega en cua", "download_enqueue": "Descàrrega en cua",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Zona horària", "edit_date_time_dialog_timezone": "Zona horària",
"edit_image_title": "Editar", "edit_image_title": "Editar",
"edit_location_dialog_title": "Ubicació", "edit_location_dialog_title": "Ubicació",
"empty_folder": "Aquesta carpeta és buida",
"end_date": "Data final", "end_date": "Data final",
"enqueued": "En cua", "enqueued": "En cua",
"enter_wifi_name": "Introdueix el nom de WiFi", "enter_wifi_name": "Introdueix el nom de WiFi",
@@ -262,32 +260,23 @@
"exif_bottom_sheet_description": "Afegeix descripció", "exif_bottom_sheet_description": "Afegeix descripció",
"exif_bottom_sheet_details": "DETALLS", "exif_bottom_sheet_details": "DETALLS",
"exif_bottom_sheet_location": "UBICACIÓ", "exif_bottom_sheet_location": "UBICACIÓ",
"exif_bottom_sheet_location_add": "Afegeix una ubicació", "exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PERSONES", "exif_bottom_sheet_people": "PERSONES",
"exif_bottom_sheet_person_add_person": "Afegir nom", "exif_bottom_sheet_person_add_person": "Afegir nom",
"exif_bottom_sheet_person_age": "Edat {}", "experimental_settings_new_asset_list_subtitle": "Work in progress",
"exif_bottom_sheet_person_age_months": "Edat {} mesos", "experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"exif_bottom_sheet_person_age_year_months": "Edat 1 any, {} mesos", "experimental_settings_subtitle": "Use at your own risk!",
"exif_bottom_sheet_person_age_years": "Edat {}",
"experimental_settings_new_asset_list_subtitle": "Treball en curs",
"experimental_settings_new_asset_list_title": "Habilita la graella de fotos experimental",
"experimental_settings_subtitle": "Utilitzeu-ho sota la vostra responsabilitat!",
"experimental_settings_title": "Experimental", "experimental_settings_title": "Experimental",
"external_network": "Xarxa externa", "external_network": "Xarxa externa",
"external_network_sheet_info": "Quan no estigui a la xarxa WiFi preferida, l'aplicació es connectarà al servidor mitjançant el primer dels URL següents a què pot arribar, començant de dalt a baix.", "external_network_sheet_info": "Quan no estigui a la xarxa WiFi preferida, l'aplicació es connectarà al servidor mitjançant el primer dels URL següents a què pot arribar, començant de dalt a baix.",
"failed": "Fallat", "failed": "Fallat",
"failed_to_load_assets": "No s'han pogut carregar els elements", "favorites": "Favorits",
"failed_to_load_folder": "No s'ha pogut carregar la carpeta",
"favorites": "Preferits",
"favorites_page_no_favorites": "No s'han trobat preferits", "favorites_page_no_favorites": "No s'han trobat preferits",
"favorites_page_title": "Preferits", "favorites_page_title": "Favorites",
"filename_search": "Nom o extensió del fitxer", "filename_search": "Nom o extensió del fitxer",
"filter": "Filtrar", "filter": "Filtrar",
"folder": "Carpeta",
"folder_not_found": "Carpeta no trobada",
"folders": "Carpetes",
"get_wifiname_error": "No s'ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi", "get_wifiname_error": "No s'ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi",
"grant_permission": "Concedir permís", "grant_permission": "Grant permission",
"haptic_feedback_switch": "Activa la resposta hàptica", "haptic_feedback_switch": "Activa la resposta hàptica",
"haptic_feedback_title": "Resposta Hàptica", "haptic_feedback_title": "Resposta Hàptica",
"header_settings_add_header_tip": "Afegeix Capçalera", "header_settings_add_header_tip": "Afegeix Capçalera",
@@ -296,43 +285,43 @@
"header_settings_header_value_input": "Valor de la capçalera", "header_settings_header_value_input": "Valor de la capçalera",
"header_settings_page_title": "Capçaleres de proxy", "header_settings_page_title": "Capçaleres de proxy",
"headers_settings_tile_subtitle": "Definiu les capçaleres de proxy que l'aplicació hauria d'enviar amb cada sol·licitud de xarxa", "headers_settings_tile_subtitle": "Definiu les capçaleres de proxy que l'aplicació hauria d'enviar amb cada sol·licitud de xarxa",
"headers_settings_tile_title": "Capçaleres proxy personalitzades", "headers_settings_tile_title": "Custom proxy headers",
"home_page_add_to_album_conflicts": "S'han afegit {added} elements a l'àlbum {album}. {failed} elements ja existeixen a l'àlbum.", "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_err_local": "Encara no es poden afegir elements locals als àlbums, ometent", "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_success": "S'han afegit {added} elements a l'àlbum {album}.", "home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_album_err_partner": "Encara no es poden afegir elements dels companys als àlbums, ometent", "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping",
"home_page_archive_err_local": "Encara no es poden arxivar elements locals, ometent", "home_page_archive_err_local": "Can not archive local assets yet, skipping",
"home_page_archive_err_partner": "No es poden arxivar els elements de companys, ometent", "home_page_archive_err_partner": "Can not archive partner assets, skipping",
"home_page_building_timeline": "Construint la línia de temps", "home_page_building_timeline": "Building the timeline",
"home_page_delete_err_partner": "No es poden suprimir els elements de companys, ometent", "home_page_delete_err_partner": "Can not delete partner assets, skipping",
"home_page_delete_remote_err_local": "Elements locals a la selecció d'eliminació remota, ometent", "home_page_delete_remote_err_local": "Elements locals a la selecció d'eliminació remota, ometent",
"home_page_favorite_err_local": "Encara no es pot afegir a preferits elements locals, ometent", "home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_partner": "Encara no es pot afegir a preferits elements de companys, ometent", "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping",
"home_page_first_time_notice": "Si és la primera vegada que utilitzes l'app, si us plau, assegura't d'escollir un àlbum de còpia de seguretat perquè la línia de temps pugui carregar fotos i vídeos als àlbums.", "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"home_page_share_err_local": "No es poden compartir els elements locals a través d'un enllaç, ometent", "home_page_share_err_local": "Can not share local assets via link, skipping",
"home_page_upload_err_limit": "Només es poden pujar un màxim de 30 elements alhora, ometent", "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping",
"ignore_icloud_photos": "Ignora fotos d'iCloud", "ignore_icloud_photos": "Ignora fotos d'iCloud",
"ignore_icloud_photos_description": "Les fotos emmagatzemades a iCloud no es penjaran al servidor Immich", "ignore_icloud_photos_description": "Les fotos emmagatzemades a iCloud no es penjaran al servidor Immich",
"image_saved_successfully": "Imatge desada", "image_saved_successfully": "Imatge desada",
"image_viewer_page_state_provider_download_error": "Error de baixada", "image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_started": "Baixada començada", "image_viewer_page_state_provider_download_started": "Descàrrega començada",
"image_viewer_page_state_provider_download_success": "Baixada amb èxit", "image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_share_error": "Error en compartir", "image_viewer_page_state_provider_share_error": "Share Error",
"invalid_date": "Data invàlida", "invalid_date": "Data invàlida",
"invalid_date_format": "Format de data invàlid", "invalid_date_format": "Format de data invàlid",
"library": "Llibreria", "library": "Llibreria",
"library_page_albums": "Àlbums", "library_page_albums": "Àlbums",
"library_page_archive": "Arxiu", "library_page_archive": "Arxiu",
"library_page_device_albums": "Àlbums al Dispositiu", "library_page_device_albums": "Àlbums al Dispositiu",
"library_page_favorites": "Preferits", "library_page_favorites": "Favorites",
"library_page_new_album": "Nou àlbum", "library_page_new_album": "New album",
"library_page_sharing": "Compartint", "library_page_sharing": "Sharing",
"library_page_sort_asset_count": "Nombre d'elements", "library_page_sort_asset_count": "Number of assets",
"library_page_sort_created": "Creat més recentment", "library_page_sort_created": "Created date",
"library_page_sort_last_modified": "Darrera modificació", "library_page_sort_last_modified": "Last modified",
"library_page_sort_most_oldest_photo": "Foto més antiga", "library_page_sort_most_oldest_photo": "Oldest photo",
"library_page_sort_most_recent_photo": "Foto més recent", "library_page_sort_most_recent_photo": "Most recent photo",
"library_page_sort_title": "Títol de l'àlbum", "library_page_sort_title": "Album title",
"local_network": "Xarxa local", "local_network": "Xarxa local",
"local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada", "local_network_sheet_info": "L'aplicació es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada",
"location_permission": "Permís d'ubicació", "location_permission": "Permís d'ubicació",
@@ -340,61 +329,61 @@
"location_picker_choose_on_map": "Escollir en el mapa", "location_picker_choose_on_map": "Escollir en el mapa",
"location_picker_latitude": "Latitud", "location_picker_latitude": "Latitud",
"location_picker_latitude_error": "Introdueix una latitud vàlida", "location_picker_latitude_error": "Introdueix una latitud vàlida",
"location_picker_latitude_hint": "Introdueix aquí la latitud", "location_picker_latitude_hint": "Enter your latitude here",
"location_picker_longitude": "Longitud", "location_picker_longitude": "Longitud",
"location_picker_longitude_error": "Introdueix una longitud vàlida", "location_picker_longitude_error": "Introdueix una longitud vàlida",
"location_picker_longitude_hint": "Introdueix aquí la longitud", "location_picker_longitude_hint": "Introdueix aquí la longitud",
"login_disabled": "S'ha desactivat l'inici de sessió", "login_disabled": "Login has been disabled",
"login_form_api_exception": "Excepció de l'API. Comproveu l'URL del servidor i torneu-ho a provar.", "login_form_api_exception": "API exception. Please check the server URL and try again.",
"login_form_back_button_text": "Enrere", "login_form_back_button_text": "Back",
"login_form_button_text": "Entra", "login_form_button_text": "Entra",
"login_form_email_hint": "elteu@correu.cat", "login_form_email_hint": "elteu@correu.cat",
"login_form_endpoint_hint": "http://ip-del-servidor:port", "login_form_endpoint_hint": "http://ip-del-servidor:port",
"login_form_endpoint_url": "URL del servidor", "login_form_endpoint_url": "URL del servidor",
"login_form_err_http": "Especifica http:// o https://", "login_form_err_http": "Especifica http:// o https://",
"login_form_err_invalid_email": "Adreça de correu electrònic no vàlida", "login_form_err_invalid_email": "Adreça de correu electrònic no vàlida",
"login_form_err_invalid_url": "URL no vàlid", "login_form_err_invalid_url": "Invalid URL",
"login_form_err_leading_whitespace": "Espai en blanc al principi", "login_form_err_leading_whitespace": "Espai en blanc al principi",
"login_form_err_trailing_whitespace": "Espai en blanc al final", "login_form_err_trailing_whitespace": "Espai en blanc al final",
"login_form_failed_get_oauth_server_config": "Error en iniciar sessió amb OAuth, comprova l'URL del servidor", "login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
"login_form_failed_get_oauth_server_disable": "La funcionalitat OAuth no està disponible en aquest servidor", "login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
"login_form_failed_login": "Error en iniciar sessió, comprova l'URL del servidor, el correu electrònic i la contrasenya.", "login_form_failed_login": "Error logging you in, check server URL, email and password",
"login_form_handshake_exception": "S'ha produït una excepció de handshake amb el servidor. Activa el suport per certificats autofirmats a la configuració si estàs fent servir un certificat autofirmat.", "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.",
"login_form_label_email": "Correu electrònic", "login_form_label_email": "Correu electrònic",
"login_form_label_password": "Contrasenya", "login_form_label_password": "Contrasenya",
"login_form_next_button": "Següent", "login_form_next_button": "Següent",
"login_form_password_hint": "contrasenya", "login_form_password_hint": "contrasenya",
"login_form_save_login": "Mantingues identificat", "login_form_save_login": "Mantingues identificat",
"login_form_server_empty": "Introdueix l'URL del servidor.", "login_form_server_empty": "Enter a server URL.",
"login_form_server_error": "No s'ha pogut connectar al servidor.", "login_form_server_error": "Could not connect to server.",
"login_password_changed_error": "S'ha produït un error en actualitzar la contrasenya", "login_password_changed_error": "There was an error updating your password",
"login_password_changed_success": "La contrasenya s'ha canviat correctament", "login_password_changed_success": "Password updated successfully",
"map_assets_in_bound": "{} foto", "map_assets_in_bound": "{} foto",
"map_assets_in_bounds": "{} fotos", "map_assets_in_bounds": "{} fotos",
"map_cannot_get_user_location": "No es pot obtenir la ubicació de l'usuari", "map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_cancel": "Cancel·la", "map_location_dialog_cancel": "Cancel",
"map_location_dialog_yes": "", "map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Utilitzar aquesta ubicació", "map_location_picker_page_use_location": "Utilitzar aquesta ubicació",
"map_location_service_disabled_content": "El servei de localització s'ha d'activar per mostrar els elements de la teva ubicació actual. Vols activar-lo ara?", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?",
"map_location_service_disabled_title": "Servei de localització desactivat", "map_location_service_disabled_title": "Location Service disabled",
"map_no_assets_in_bounds": "No hi ha fotos en aquesta zona", "map_no_assets_in_bounds": "No photos in this area",
"map_no_location_permission_content": "Es necessita el permís de localització per mostrar els elements de la teva ubicació actual. Vols permetre-ho ara?", "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
"map_no_location_permission_title": "Permís de localització denegat", "map_no_location_permission_title": "Location Permission denied",
"map_settings_dark_mode": "Mode fosc", "map_settings_dark_mode": "Dark mode",
"map_settings_date_range_option_all": "Tot", "map_settings_date_range_option_all": "All",
"map_settings_date_range_option_day": "Últimes 24 hores", "map_settings_date_range_option_day": "Últimes 24 hores",
"map_settings_date_range_option_days": "Darrers {} dies", "map_settings_date_range_option_days": "Darrers {} dies",
"map_settings_date_range_option_year": "Any passat", "map_settings_date_range_option_year": "Any passat",
"map_settings_date_range_option_years": "Darrers {} anys", "map_settings_date_range_option_years": "Darrers {} anys",
"map_settings_dialog_cancel": "Cancel·la", "map_settings_dialog_cancel": "Cancel",
"map_settings_dialog_save": "Desa", "map_settings_dialog_save": "Save",
"map_settings_dialog_title": "Configuració del mapa", "map_settings_dialog_title": "Map Settings",
"map_settings_include_show_archived": "Incloure arxivats", "map_settings_include_show_archived": "Include Archived",
"map_settings_include_show_partners": "Incloure companys", "map_settings_include_show_partners": "Incloure companys",
"map_settings_only_relative_range": "Rang de dates", "map_settings_only_relative_range": "Date range",
"map_settings_only_show_favorites": "Mostra només preferits", "map_settings_only_show_favorites": "Show Favorite Only",
"map_settings_theme_settings": "Tema del Mapa", "map_settings_theme_settings": "Tema del Mapa",
"map_zoom_to_see_photos": "Allunya per veure fotos", "map_zoom_to_see_photos": "Zoom out to see photos",
"memories_all_caught_up": "Posat al dia", "memories_all_caught_up": "Posat al dia",
"memories_check_back_tomorrow": "Torna demà per veure més records", "memories_check_back_tomorrow": "Torna demà per veure més records",
"memories_start_over": "Torna a començar", "memories_start_over": "Torna a començar",
@@ -402,7 +391,7 @@
"memories_year_ago": "Fa un any", "memories_year_ago": "Fa un any",
"memories_years_ago": "Fa {} anys", "memories_years_ago": "Fa {} anys",
"monthly_title_text_date_format": "MMMM y", "monthly_title_text_date_format": "MMMM y",
"motion_photos_page_title": "Fotos en moviment", "motion_photos_page_title": "Motion Photos",
"multiselect_grid_edit_date_time_err_read_only": "No es pot canviar la data del fitxer(s) de només lectura, ometent", "multiselect_grid_edit_date_time_err_read_only": "No es pot canviar la data del fitxer(s) de només lectura, ometent",
"multiselect_grid_edit_gps_err_read_only": "No es pot canviar la localització de fitxers de només lectura. Saltant.", "multiselect_grid_edit_gps_err_read_only": "No es pot canviar la localització de fitxers de només lectura. Saltant.",
"my_albums": "Els meus àlbums", "my_albums": "Els meus àlbums",
@@ -411,58 +400,58 @@
"no_assets_to_show": "No hi ha elements per mostrar", "no_assets_to_show": "No hi ha elements per mostrar",
"no_name": "Sense nom", "no_name": "Sense nom",
"notification_permission_dialog_cancel": "Cancel·la", "notification_permission_dialog_cancel": "Cancel·la",
"notification_permission_dialog_content": "Per activar les notificacions, aneu a Configuració i seleccioneu permet.", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Configuració", "notification_permission_dialog_settings": "Configuració",
"notification_permission_list_tile_content": "Atorga permís per a activar les notificacions.", "notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Activa les notificacions", "notification_permission_list_tile_enable_button": "Activa les notificacions",
"notification_permission_list_tile_title": "Permís de notificacions", "notification_permission_list_tile_title": "Notification Permission",
"not_selected": "No seleccionat", "not_selected": "No seleccionat",
"on_this_device": "En aquest dispositiu", "on_this_device": "En aquest dispositiu",
"partner_list_user_photos": "fotos de {user}", "partner_list_user_photos": "fotos de {user}",
"partner_list_view_all": "Veure tot", "partner_list_view_all": "Veure tot",
"partner_page_add_partner": "Afegeix company", "partner_page_add_partner": "Afegeix company",
"partner_page_empty_message": "Les teves fotos encara no estan compartides amb cap company.", "partner_page_empty_message": "Your photos are not yet shared with any partner.",
"partner_page_no_more_users": "No hi ha més usuaris a afegir", "partner_page_no_more_users": "No more users to add",
"partner_page_partner_add_failed": "No s'ha pogut afegir el company", "partner_page_partner_add_failed": "Failed to add partner",
"partner_page_select_partner": "Escull company", "partner_page_select_partner": "Escull company",
"partner_page_shared_to_title": "Compartit amb", "partner_page_shared_to_title": "Compartit amb",
"partner_page_stop_sharing_content": "{} ja no podrà accedir a les teves fotos.", "partner_page_stop_sharing_content": "{} will no longer be able to access your photos.",
"partner_page_stop_sharing_title": "Vols deixar de compartir les teves fotos?", "partner_page_stop_sharing_title": "Stop sharing your photos?",
"partner_page_title": "Company", "partner_page_title": "Company",
"partners": "Companys", "partners": "Companys",
"paused": "Pausat", "paused": "Pausat",
"people": "Persones", "people": "Persones",
"permission_onboarding_back": "Torna", "permission_onboarding_back": "Back",
"permission_onboarding_continue_anyway": "Continua de totes maneres", "permission_onboarding_continue_anyway": "Continue anyway",
"permission_onboarding_get_started": "Comença", "permission_onboarding_get_started": "Get started",
"permission_onboarding_go_to_settings": "Ves a la configuració", "permission_onboarding_go_to_settings": "Go to settings",
"permission_onboarding_grant_permission": "Dona permisos", "permission_onboarding_grant_permission": "Dona permisos",
"permission_onboarding_log_out": "Tanca la sessió", "permission_onboarding_log_out": "Log out",
"permission_onboarding_permission_denied": "S'ha denegat el permís. Per utilitzar Immich, concediu permisos de fotos i vídeos a Configuració.", "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
"permission_onboarding_permission_granted": "Permís concedit! Tot a punt.", "permission_onboarding_permission_granted": "Permission granted! You are all set.",
"permission_onboarding_permission_limited": "Permís limitat. Per a permetre que Immich faci còpies de seguretat i gestioni tota la col·lecció de la galeria, concediu permisos de fotos i vídeos a Configuració.", "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
"permission_onboarding_request": "Immich requereix permís per veure les vostres fotos i vídeos.", "permission_onboarding_request": "Immich requires permission to view your photos and videos.",
"places": "Llocs", "places": "Llocs",
"preferences_settings_subtitle": "Gestiona les preferències de l'aplicació", "preferences_settings_subtitle": "Gestiona les preferències de l'aplicació",
"preferences_settings_title": "Preferències", "preferences_settings_title": "Preferències",
"profile_drawer_app_logs": "Registres", "profile_drawer_app_logs": "Logs",
"profile_drawer_client_out_of_date_major": "L'aplicació mòbil està desactualitzada. Si us plau, actualitzeu a l'última versió major.", "profile_drawer_client_out_of_date_major": "Mobile App is out of date. Please update to the latest major version.",
"profile_drawer_client_out_of_date_minor": "L'aplicació mòbil està desactualitzada. Si us plau, actualitzeu a l'última versió menor.", "profile_drawer_client_out_of_date_minor": "Mobile App is out of date. Please update to the latest minor version.",
"profile_drawer_client_server_up_to_date": "El Client i el Servidor estan actualitzats", "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
"profile_drawer_documentation": "Documentació", "profile_drawer_documentation": "Documentation",
"profile_drawer_github": "GitHub", "profile_drawer_github": "GitHub",
"profile_drawer_server_out_of_date_major": "L'aplicació mòbil està desactualitzada. Si us plau, actualitzeu a l'última versió major.", "profile_drawer_server_out_of_date_major": "Server is out of date. Please update to the latest major version.",
"profile_drawer_server_out_of_date_minor": "L'aplicació mòbil està desactualitzada. Si us plau, actualitzeu a l'última versió menor.", "profile_drawer_server_out_of_date_minor": "Server is out of date. Please update to the latest minor version.",
"profile_drawer_settings": "Configuració", "profile_drawer_settings": "Settings",
"profile_drawer_sign_out": "Tanca la sessió", "profile_drawer_sign_out": "Tanca la sessió",
"profile_drawer_trash": "Paperera", "profile_drawer_trash": "Trash",
"recently_added": "Afegit recentment", "recently_added": "Afegit recentment",
"recently_added_page_title": "Afegit recentment", "recently_added_page_title": "Recently Added",
"save": "Desa", "save": "Desa",
"save_to_gallery": "Desa a galeria", "save_to_gallery": "Desa a galeria",
"scaffold_body_error_occurred": "S'ha produït un error", "scaffold_body_error_occurred": "S'ha produït un error",
"search_albums": "Cerca àlbums", "search_albums": "Cerca àlbums",
"search_bar_hint": "Cerca les teves fotos", "search_bar_hint": "Search your photos",
"search_filter_apply": "Aplicar filtre", "search_filter_apply": "Aplicar filtre",
"search_filter_camera": "Càmera", "search_filter_camera": "Càmera",
"search_filter_camera_make": "Marca", "search_filter_camera_make": "Marca",
@@ -474,7 +463,7 @@
"search_filter_date_title": "Selecciona un rang de dates", "search_filter_date_title": "Selecciona un rang de dates",
"search_filter_description": "Cerca per descripció", "search_filter_description": "Cerca per descripció",
"search_filter_display_option_archive": "Arxivat", "search_filter_display_option_archive": "Arxivat",
"search_filter_display_option_favorite": "Preferit", "search_filter_display_option_favorite": "Favorit",
"search_filter_display_option_not_in_album": "No en àlbum", "search_filter_display_option_not_in_album": "No en àlbum",
"search_filter_display_options": "Opcions de Visualització", "search_filter_display_options": "Opcions de Visualització",
"search_filter_display_options_title": "Opcions de visualització", "search_filter_display_options_title": "Opcions de visualització",
@@ -497,60 +486,60 @@
"search_page_categories": "Categories", "search_page_categories": "Categories",
"search_page_favorites": "Preferides", "search_page_favorites": "Preferides",
"search_page_motion_photos": "Fotografies animades", "search_page_motion_photos": "Fotografies animades",
"search_page_no_objects": "No hi ha informació d'objectes disponibles", "search_page_no_objects": "No Objects Info Available",
"search_page_no_places": "No hi ha informació de llocs disponibles", "search_page_no_places": "No Places Info Available",
"search_page_people": "Persones", "search_page_people": "Persones",
"search_page_person_add_name_dialog_cancel": "Cancel·la", "search_page_person_add_name_dialog_cancel": "Cancel",
"search_page_person_add_name_dialog_hint": "Nom", "search_page_person_add_name_dialog_hint": "Name",
"search_page_person_add_name_dialog_save": "Desa", "search_page_person_add_name_dialog_save": "Save",
"search_page_person_add_name_dialog_title": "Afegeix un nom", "search_page_person_add_name_dialog_title": "Add a name",
"search_page_person_add_name_subtitle": "Troba'ls ràpid buscant per nom", "search_page_person_add_name_subtitle": "Find them fast by name with search",
"search_page_person_add_name_title": "Afegeix un nom", "search_page_person_add_name_title": "Add a name",
"search_page_person_edit_name": "Edita el nom", "search_page_person_edit_name": "Edit name",
"search_page_places": "Llocs", "search_page_places": "Llocs",
"search_page_recently_added": "Afegit recentment", "search_page_recently_added": "Afegit recentment",
"search_page_screenshots": "Captures de pantalla", "search_page_screenshots": "Captures de pantalla",
"search_page_search_photos_videos": "Cerca les teves fotos i vídeos", "search_page_search_photos_videos": "Cerca les teves fotos i vídeos",
"search_page_selfies": "Autofotos", "search_page_selfies": "Autofotos",
"search_page_things": "Coses", "search_page_things": "Coses",
"search_page_videos": "Vídeos", "search_page_videos": "Videos",
"search_page_view_all_button": "Veure tot", "search_page_view_all_button": "Veure tot",
"search_page_your_activity": "La teva activitat", "search_page_your_activity": "Your activity",
"search_page_your_map": "El teu mapa", "search_page_your_map": "Your Map",
"search_result_page_new_search_hint": "Cerca nova", "search_result_page_new_search_hint": "Cerca nova",
"search_suggestion_list_smart_search_hint_1": "La cerca intel·ligent està habilitada per defecte, per a cercar metadades utilitzeu la sintaxi ", "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ",
"search_suggestion_list_smart_search_hint_2": "m:el-teu-terme-de-cerca", "search_suggestion_list_smart_search_hint_2": "m:your-search-term",
"select_additional_user_for_sharing_page_suggestions": "Suggeriments", "select_additional_user_for_sharing_page_suggestions": "Suggeriments",
"select_user_for_sharing_page_err_album": "Error al crear l'àlbum", "select_user_for_sharing_page_err_album": "Error al crear l'àlbum",
"select_user_for_sharing_page_share_suggestions": "Suggeriments", "select_user_for_sharing_page_share_suggestions": "Suggestions",
"server_endpoint": "Endpoint de Servidor", "server_endpoint": "Endpoint de Servidor",
"server_info_box_app_version": "Versió de l'aplicació", "server_info_box_app_version": "Versió de l'aplicació",
"server_info_box_latest_release": "Última versió", "server_info_box_latest_release": "Latest Version",
"server_info_box_server_url": "URL del servidor", "server_info_box_server_url": "Server URL",
"server_info_box_server_version": "Versió del servidor", "server_info_box_server_version": "Versió del servidor",
"setting_image_viewer_help": "El visor de detalls carrega primer la miniatura petita, després carrega la vista prèvia de mida mitjana (si està habilitada), finalment carrega l'original (si està habilitada).", "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": "Activa per carregar la imatge en resolució original (molt gran!). Desactiva per reduir el consum de dades (tant de xarxa com de memòria cau).", "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": "Carrega la imatge original", "setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Activa per carregar una imatge de resolució mitjana. Desactiva per carregar directament la imatge original, o bé utilitzar només la miniatura.", "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Carrega la imatge de vista prèvia", "setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_title": "Imatges", "setting_image_viewer_title": "Imatges",
"setting_languages_apply": "Aplicar", "setting_languages_apply": "Aplicar",
"setting_languages_subtitle": "Canvia el llenguatge de l'aplicació", "setting_languages_subtitle": "Canvia el llenguatge de l'aplicació",
"setting_languages_title": "Idiomes", "setting_languages_title": "Idiomes",
"setting_notifications_notify_failures_grace_period": "Notifica les fallades de la còpia de seguretat en segon pla: {}", "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
"setting_notifications_notify_hours": "{} hores", "setting_notifications_notify_hours": "{} hours",
"setting_notifications_notify_immediately": "immediatament", "setting_notifications_notify_immediately": "immediately",
"setting_notifications_notify_minutes": "{} minuts", "setting_notifications_notify_minutes": "{} minutes",
"setting_notifications_notify_never": "mai", "setting_notifications_notify_never": "never",
"setting_notifications_notify_seconds": "{} segons", "setting_notifications_notify_seconds": "{} seconds",
"setting_notifications_single_progress_subtitle": "Informació detallada del progrés de la pujada de cada fitxer", "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
"setting_notifications_single_progress_title": "Mostra el progrés detallat de la còpia de seguretat en segon pla", "setting_notifications_single_progress_title": "Show background backup detail progress",
"setting_notifications_subtitle": "Ajusta les preferències de notificació", "setting_notifications_subtitle": "Adjust your notification preferences",
"setting_notifications_title": "Notificacions", "setting_notifications_title": "Notifications",
"setting_notifications_total_progress_subtitle": "Progrés general de la pujada (elements completats/total)", "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Mostra el progrés total de la còpia de seguretat en segon pla", "setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Configuració", "setting_pages_app_bar_settings": "Settings",
"settings_require_restart": "Si us plau, reinicieu Immich per a aplicar aquest canvi", "settings_require_restart": "Please restart Immich to apply this setting",
"setting_video_viewer_looping_subtitle": "Habilita per reproduir automàticament un vídeo al visualitzador de detalls.", "setting_video_viewer_looping_subtitle": "Habilita per reproduir automàticament un vídeo al visualitzador de detalls.",
"setting_video_viewer_looping_title": "Bucle", "setting_video_viewer_looping_title": "Bucle",
"setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificació disponible. Pot conduir a l'amortització. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuració.", "setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificació disponible. Pot conduir a l'amortització. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuració.",
@@ -561,33 +550,33 @@
"share_add_title": "Afegeix un títol", "share_add_title": "Afegeix un títol",
"share_assets_selected": "{} seleccionats", "share_assets_selected": "{} seleccionats",
"share_create_album": "Crea un àlbum", "share_create_album": "Crea un àlbum",
"shared_album_activities_input_disable": "Els comentaris estan desactivats", "shared_album_activities_input_disable": "Comment is disabled",
"shared_album_activities_input_hint": "Digues alguna cosa", "shared_album_activities_input_hint": "Say something",
"shared_album_activity_remove_content": "Voleu eliminar aquesta activitat?", "shared_album_activity_remove_content": "Do you want to delete this activity?",
"shared_album_activity_remove_title": "Elimina l'activitat", "shared_album_activity_remove_title": "Delete Activity",
"shared_album_activity_setting_subtitle": "Permet que altres responguin", "shared_album_activity_setting_subtitle": "Let others respond",
"shared_album_activity_setting_title": "Comentaris i m'agrada", "shared_album_activity_setting_title": "Comments & likes",
"shared_album_section_people_action_error": "S'ha produït un error en retirar-se/eliminar l'àlbum", "shared_album_section_people_action_error": "Error leaving/removing from album",
"shared_album_section_people_action_leave": "Elimina l'usuari de l'àlbum", "shared_album_section_people_action_leave": "Remove user from album",
"shared_album_section_people_action_remove_user": "Elimina l'usuari de l'àlbum", "shared_album_section_people_action_remove_user": "Remove user from album",
"shared_album_section_people_owner_label": "Amo", "shared_album_section_people_owner_label": "Owner",
"shared_album_section_people_title": "PERSONES", "shared_album_section_people_title": "PEOPLE",
"share_dialog_preparing": "S'està preparant...", "share_dialog_preparing": "Preparing...",
"shared_intent_upload_button_progress_text": "{} / {} Pujat", "shared_intent_upload_button_progress_text": "{} / {} Pujat",
"shared_link_app_bar_title": "Enllaços compartits", "shared_link_app_bar_title": "Shared Links",
"shared_link_clipboard_copied_massage": "S'ha copiat al porta-retalls", "shared_link_clipboard_copied_massage": "Copied to clipboard",
"shared_link_clipboard_text": "Enllaç: {}\nContrasenya: {}", "shared_link_clipboard_text": "Link: {}\nPassword: {}",
"shared_link_create_app_bar_title": "Crea un enllaç per compartir", "shared_link_create_app_bar_title": "Create link to share",
"shared_link_create_error": "S'ha produït un error en crear l'enllaç compartit", "shared_link_create_error": "Error while creating shared link",
"shared_link_create_info": "Permet que qualsevol persona amb l'enllaç vegi les fotografies seleccionades", "shared_link_create_info": "Let anyone with the link see the selected photo(s)",
"shared_link_create_submit_button": "Crea enllaç", "shared_link_create_submit_button": "Create link",
"shared_link_edit_allow_download": "Permet que usuaris públics descarreguin", "shared_link_edit_allow_download": "Allow public user to download",
"shared_link_edit_allow_upload": "Permet que usuaris públics publiquin", "shared_link_edit_allow_upload": "Allow public user to upload",
"shared_link_edit_app_bar_title": "Edita l'enllaç", "shared_link_edit_app_bar_title": "Edit link",
"shared_link_edit_change_expiry": "Canvia la data de caducitat", "shared_link_edit_change_expiry": "Change expiration time",
"shared_link_edit_description": "Descripció", "shared_link_edit_description": "Description",
"shared_link_edit_description_hint": "Introduïu la descripció de compartició", "shared_link_edit_description_hint": "Enter the share description",
"shared_link_edit_expire_after": "Caduca després de", "shared_link_edit_expire_after": "Expire after",
"shared_link_edit_expire_after_option_day": "1 dia", "shared_link_edit_expire_after_option_day": "1 dia",
"shared_link_edit_expire_after_option_days": "{} dies", "shared_link_edit_expire_after_option_days": "{} dies",
"shared_link_edit_expire_after_option_hour": "1 hora", "shared_link_edit_expire_after_option_hour": "1 hora",
@@ -595,98 +584,98 @@
"shared_link_edit_expire_after_option_minute": "1 minut", "shared_link_edit_expire_after_option_minute": "1 minut",
"shared_link_edit_expire_after_option_minutes": "{} minuts", "shared_link_edit_expire_after_option_minutes": "{} minuts",
"shared_link_edit_expire_after_option_months": "{} mesos", "shared_link_edit_expire_after_option_months": "{} mesos",
"shared_link_edit_expire_after_option_never": "Mai", "shared_link_edit_expire_after_option_never": "Never",
"shared_link_edit_expire_after_option_year": "any {}", "shared_link_edit_expire_after_option_year": "any {}",
"shared_link_edit_password": "Contrasenya", "shared_link_edit_password": "Password",
"shared_link_edit_password_hint": "Introduïu la contrasenya de compartició", "shared_link_edit_password_hint": "Enter the share password",
"shared_link_edit_show_meta": "Mostra metadades", "shared_link_edit_show_meta": "Show metadata",
"shared_link_edit_submit_button": "Actualitza l'enllaç", "shared_link_edit_submit_button": "Update link",
"shared_link_empty": "No tens cap enllaç compartit", "shared_link_empty": "You don't have any shared links",
"shared_link_error_server_url_fetch": "No s'ha pogut obtenir l'URL del servidor", "shared_link_error_server_url_fetch": "Cannot fetch the server url",
"shared_link_expired": "Caducat", "shared_link_expired": "Expired",
"shared_link_expires_day": "Caduca d'aquí a {} dia", "shared_link_expires_day": "Caduca d'aquí a {} dia",
"shared_link_expires_days": "Caduca d'aquí a {} dies", "shared_link_expires_days": "Caduca d'aquí a {} dies",
"shared_link_expires_hour": "Caduca d'aquí a {} hora", "shared_link_expires_hour": "Caduca d'aquí a {} hora",
"shared_link_expires_hours": "Caduca d'aquí a {} hores", "shared_link_expires_hours": "Caduca d'aquí a {} hores",
"shared_link_expires_minute": "Caduca d'aquí a {} minut", "shared_link_expires_minute": "Caduca d'aquí a {} minut",
"shared_link_expires_minutes": "Caduca d'aquí a {} minuts", "shared_link_expires_minutes": "Expires in {} minutes",
"shared_link_expires_never": "Caduca ∞", "shared_link_expires_never": "Expires ∞",
"shared_link_expires_second": "Caduca d'aquí a {} segon", "shared_link_expires_second": "Caduca d'aquí a {} segon",
"shared_link_expires_seconds": "Caduca d'aquí a {} segons", "shared_link_expires_seconds": "Expires in {} seconds",
"shared_link_individual_shared": "Individual compartit", "shared_link_individual_shared": "Individual compartit",
"shared_link_info_chip_download": "Baixa", "shared_link_info_chip_download": "Download",
"shared_link_info_chip_metadata": "EXIF", "shared_link_info_chip_metadata": "EXIF",
"shared_link_info_chip_upload": "Puja", "shared_link_info_chip_upload": "Puja",
"shared_link_manage_links": "Gestiona els enllaços compartits", "shared_link_manage_links": "Manage Shared links",
"shared_link_public_album": "Àlbum públic", "shared_link_public_album": "Àlbum públic",
"shared_links": "Enllaços compartits", "shared_links": "Enllaços compartits",
"share_done": "Fet", "share_done": "Done",
"shared_with_me": "Compartit amb mi", "shared_with_me": "Compartit amb mi",
"share_invite": "Convida a l'àlbum", "share_invite": "Convida a l'àlbum",
"sharing_page_album": "Àlbums compartits", "sharing_page_album": "Shared albums",
"sharing_page_description": "Crea àlbums compartits per compartir fotos i vídeos amb persones de la teva xarxa.", "sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
"sharing_page_empty_list": "LLISTA BUIDA", "sharing_page_empty_list": "EMPTY LIST",
"sharing_silver_appbar_create_shared_album": "Crea àlbum compartit", "sharing_silver_appbar_create_shared_album": "Crea àlbum compartit",
"sharing_silver_appbar_shared_links": "Enllaços compartits", "sharing_silver_appbar_shared_links": "Shared links",
"sharing_silver_appbar_share_partner": "Comparteix amb un company", "sharing_silver_appbar_share_partner": "Comparteix amb un company",
"start_date": "Data inicial", "start_date": "Data inicial",
"sync": "Sincronitzar", "sync": "Sincronitzar",
"sync_albums": "Sincronitzar àlbums", "sync_albums": "Sincronitzar àlbums",
"sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de còpia de seguretat seleccionats", "sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de còpia de seguretat seleccionats",
"sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich", "sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich",
"tab_controller_nav_library": "Llibreria", "tab_controller_nav_library": "Library",
"tab_controller_nav_photos": "Fotografies", "tab_controller_nav_photos": "Fotografies",
"tab_controller_nav_search": "Cerca", "tab_controller_nav_search": "Cerca",
"tab_controller_nav_sharing": "Compartint", "tab_controller_nav_sharing": "Compartint",
"theme_setting_asset_list_storage_indicator_title": "Mostra l'indicador d'emmagatzematge als títols dels elements", "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
"theme_setting_asset_list_tiles_per_row_title": "Nombre d'elements per fila ({})", "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
"theme_setting_colorful_interface_subtitle": "Apliqueu color primari a les superfícies de fons.", "theme_setting_colorful_interface_subtitle": "Apliqueu color primari a les superfícies de fons.",
"theme_setting_colorful_interface_title": "Interfície colorida", "theme_setting_colorful_interface_title": "Interfície colorida",
"theme_setting_dark_mode_switch": "Tema fosc", "theme_setting_dark_mode_switch": "Dark mode",
"theme_setting_image_viewer_quality_subtitle": "Ajusta la qualitat del visor de detalls d'imatges", "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
"theme_setting_image_viewer_quality_title": "Qualitat del visor d'imatges", "theme_setting_image_viewer_quality_title": "Image viewer quality",
"theme_setting_primary_color_subtitle": "Trieu un color per a les accions i els accents principals.", "theme_setting_primary_color_subtitle": "Trieu un color per a les accions i els accents principals.",
"theme_setting_primary_color_title": "Color primari", "theme_setting_primary_color_title": "Color primari",
"theme_setting_system_primary_color_title": "Utilitza color de sistema", "theme_setting_system_primary_color_title": "Utilitza color de sistema",
"theme_setting_system_theme_switch": "Automàtic (Segueix la configuració del sistema)", "theme_setting_system_theme_switch": "Automatic (Follow system setting)",
"theme_setting_theme_subtitle": "Trieu la configuració del tema de l'aplicació", "theme_setting_theme_subtitle": "Choose the app's theme setting",
"theme_setting_theme_title": "Tema", "theme_setting_theme_title": "Theme",
"theme_setting_three_stage_loading_subtitle": "La càrrega en tres etapes podria augmentar el rendiment de càrrega, però causa un consum de xarxa significativament més alt", "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
"theme_setting_three_stage_loading_title": "Activa la càrrega en tres etapes", "theme_setting_three_stage_loading_title": "Enable three-stage loading",
"translated_text_options": "Opcions", "translated_text_options": "Options",
"trash": "Paperera", "trash": "Paperera",
"trash_emptied": "Paperera buidada", "trash_emptied": "Paperera buidada",
"trash_page_delete": "Elimina", "trash_page_delete": "Delete",
"trash_page_delete_all": "Eliminar-ho tot", "trash_page_delete_all": "Delete All",
"trash_page_empty_trash_btn": "Buida la paperera", "trash_page_empty_trash_btn": "Empty trash",
"trash_page_empty_trash_dialog_content": "Segur que voleu eliminar els elements? Aquests elements seran eliminats permanentment de Immich", "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich",
"trash_page_empty_trash_dialog_ok": "", "trash_page_empty_trash_dialog_ok": "Ok",
"trash_page_info": "Els elements que s'enviïn a la paperera s'eliminaran permanentment després de {} dies", "trash_page_info": "Trashed items will be permanently deleted after {} days",
"trash_page_no_assets": "No hi ha elements a la paperera", "trash_page_no_assets": "No trashed assets",
"trash_page_restore": "Restaura", "trash_page_restore": "Restore",
"trash_page_restore_all": "Restaura-ho tot", "trash_page_restore_all": "Restore All",
"trash_page_select_assets_btn": "Selecciona elements", "trash_page_select_assets_btn": "Select assets",
"trash_page_select_btn": "Selecciona", "trash_page_select_btn": "Select",
"trash_page_title": "Paperera ({})", "trash_page_title": "Trash ({})",
"upload": "Puja", "upload": "Puja",
"upload_dialog_cancel": "Cancel·la", "upload_dialog_cancel": "Cancel",
"upload_dialog_info": "Vols fer còpia de seguretat dels elements seleccionats al servidor?", "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
"upload_dialog_ok": "Puja", "upload_dialog_ok": "Upload",
"upload_dialog_title": "Puja elements", "upload_dialog_title": "Upload Asset",
"uploading": "Pujant", "uploading": "Pujant",
"upload_to_immich": "Puja a Immich ({})", "upload_to_immich": "Puja a Immich ({})",
"use_current_connection": "utilitzar la connexió actual", "use_current_connection": "utilitzar la connexió actual",
"validate_endpoint_error": "Per favor introdueix un URL vàlid", "validate_endpoint_error": "Per favor introdueix un URL vàlid",
"version_announcement_overlay_ack": "D'acord", "version_announcement_overlay_ack": "Acknowledge",
"version_announcement_overlay_release_notes": "notes de llançament", "version_announcement_overlay_release_notes": "release notes",
"version_announcement_overlay_text_1": "Hola amic, hi ha una nova versió d'", "version_announcement_overlay_text_1": "Hi friend, there is a new release of",
"version_announcement_overlay_text_2": "si us plau, pren-te una estona per visitar les ", "version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " i assegura't que la teva configuració de docker-compose i .env estiguin actualitzades per evitar qualsevol error de configuració, especialment si utilitzes WatchTower o qualsevol mecanisme que gestioni l'actualització automàtica de l'aplicació del servidor.", "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "Nova versió del servidor disponible \uD83C\uDF89", "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"videos": "Vídeos", "videos": "Vídeos",
"viewer_remove_from_stack": "Elimina de la pila", "viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Fes servir com a element principal", "viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Desapila", "viewer_unstack": "Un-Stack",
"wifi_name": "Nom WiFi", "wifi_name": "Nom WiFi",
"your_wifi_name": "El teu nom WiFi" "your_wifi_name": "El teu nom WiFi"
} }
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Upravit datum a čas", "control_bottom_app_bar_edit_time": "Upravit datum a čas",
"control_bottom_app_bar_favorite": "Oblíbené", "control_bottom_app_bar_favorite": "Oblíbené",
"control_bottom_app_bar_share": "Sdílet", "control_bottom_app_bar_share": "Sdílet",
"control_bottom_app_bar_share_link": "Sdílet odkaz",
"control_bottom_app_bar_share_to": "Sdílet v", "control_bottom_app_bar_share_to": "Sdílet v",
"control_bottom_app_bar_stack": "Zásobník", "control_bottom_app_bar_stack": "Zásobník",
"control_bottom_app_bar_trash_from_immich": "Přesunout do koše", "control_bottom_app_bar_trash_from_immich": "Přesunout do koše",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Časové pásmo", "edit_date_time_dialog_timezone": "Časové pásmo",
"edit_image_title": "Upravit", "edit_image_title": "Upravit",
"edit_location_dialog_title": "Poloha", "edit_location_dialog_title": "Poloha",
"empty_folder": "Tato složka je prázdná",
"end_date": "Konečné datum", "end_date": "Konečné datum",
"enqueued": "Ve frontě", "enqueued": "Ve frontě",
"enter_wifi_name": "Zadejte název WiFi", "enter_wifi_name": "Zadejte název WiFi",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "Přidat polohu", "exif_bottom_sheet_location_add": "Přidat polohu",
"exif_bottom_sheet_people": "LIDÉ", "exif_bottom_sheet_people": "LIDÉ",
"exif_bottom_sheet_person_add_person": "Přidat jméno", "exif_bottom_sheet_person_add_person": "Přidat jméno",
"exif_bottom_sheet_person_age": "{} let",
"exif_bottom_sheet_person_age_months": "{} měsíců",
"exif_bottom_sheet_person_age_year_months": "1 rok a {} měsíců",
"exif_bottom_sheet_person_age_years": "{} let",
"experimental_settings_new_asset_list_subtitle": "Zpracovávám", "experimental_settings_new_asset_list_subtitle": "Zpracovávám",
"experimental_settings_new_asset_list_title": "Povolení experimentální mřížky fotografií", "experimental_settings_new_asset_list_title": "Povolení experimentální mřížky fotografií",
"experimental_settings_subtitle": "Používejte na vlastní riziko!", "experimental_settings_subtitle": "Používejte na vlastní riziko!",
@@ -276,16 +270,11 @@
"external_network": "Externí síť", "external_network": "Externí síť",
"external_network_sheet_info": "Pokud nejste v preferované síti WiFi, aplikace se připojí k serveru prostřednictvím první z níže uvedených adres URL, které může dosáhnout, počínaje shora dolů", "external_network_sheet_info": "Pokud nejste v preferované síti WiFi, aplikace se připojí k serveru prostřednictvím první z níže uvedených adres URL, které může dosáhnout, počínaje shora dolů",
"failed": "Selhalo", "failed": "Selhalo",
"failed_to_load_assets": "Nepodařilo se načíst položky",
"failed_to_load_folder": "Nepodařilo se načíst složku",
"favorites": "Oblíbené", "favorites": "Oblíbené",
"favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média", "favorites_page_no_favorites": "Nebyla nalezena žádná oblíbená média",
"favorites_page_title": "Oblíbené", "favorites_page_title": "Oblíbené",
"filename_search": "Název nebo přípona souboru", "filename_search": "Název nebo přípona souboru",
"filter": "Filtr", "filter": "Filtr",
"folder": "Složka",
"folder_not_found": "Složka nebyla nalezena",
"folders": "Složky",
"get_wifiname_error": "Nepodařilo se získat název Wi-Fi. Zkontrolujte, zda jste udělili potřebná oprávnění a zda jste připojeni k Wi-Fi síti", "get_wifiname_error": "Nepodařilo se získat název Wi-Fi. Zkontrolujte, zda jste udělili potřebná oprávnění a zda jste připojeni k Wi-Fi síti",
"grant_permission": "Udělit oprávnění", "grant_permission": "Udělit oprávnění",
"haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu", "haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu",
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Rediger tid og dato", "control_bottom_app_bar_edit_time": "Rediger tid og dato",
"control_bottom_app_bar_favorite": "Favorit", "control_bottom_app_bar_favorite": "Favorit",
"control_bottom_app_bar_share": "Del", "control_bottom_app_bar_share": "Del",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "Del til", "control_bottom_app_bar_share_to": "Del til",
"control_bottom_app_bar_stack": "Stak", "control_bottom_app_bar_stack": "Stak",
"control_bottom_app_bar_trash_from_immich": "Flyt til papirkurv", "control_bottom_app_bar_trash_from_immich": "Flyt til papirkurv",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Tidszone", "edit_date_time_dialog_timezone": "Tidszone",
"edit_image_title": "Rediger", "edit_image_title": "Rediger",
"edit_location_dialog_title": "Placering", "edit_location_dialog_title": "Placering",
"empty_folder": "This folder is empty",
"end_date": "End date", "end_date": "End date",
"enqueued": "Enqueued", "enqueued": "Enqueued",
"enter_wifi_name": "Indtast WiFi-navn", "enter_wifi_name": "Indtast WiFi-navn",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "Tilføj en placering", "exif_bottom_sheet_location_add": "Tilføj en placering",
"exif_bottom_sheet_people": "PERSONER", "exif_bottom_sheet_people": "PERSONER",
"exif_bottom_sheet_person_add_person": "Tilføj navn", "exif_bottom_sheet_person_add_person": "Tilføj navn",
"exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "Under udarbejdelse", "experimental_settings_new_asset_list_subtitle": "Under udarbejdelse",
"experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter", "experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter",
"experimental_settings_subtitle": "Brug på eget ansvar!", "experimental_settings_subtitle": "Brug på eget ansvar!",
@@ -276,16 +270,11 @@
"external_network": "Eksternt netværk", "external_network": "Eksternt netværk",
"external_network_sheet_info": "Nå der er ikke er forbundet til det foretrukne WiFi-netværk, vil appen forbinde til den første URL, den kan forbinde til, på listen nedenfor. Startende med i toppen", "external_network_sheet_info": "Nå der er ikke er forbundet til det foretrukne WiFi-netværk, vil appen forbinde til den første URL, den kan forbinde til, på listen nedenfor. Startende med i toppen",
"failed": "Failed", "failed": "Failed",
"failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder",
"favorites": "Favoritter", "favorites": "Favoritter",
"favorites_page_no_favorites": "Ingen favoritter blev fundet", "favorites_page_no_favorites": "Ingen favoritter blev fundet",
"favorites_page_title": "Favoritter", "favorites_page_title": "Favoritter",
"filename_search": "File name or extension", "filename_search": "File name or extension",
"filter": "Filter", "filter": "Filter",
"folder": "Folder",
"folder_not_found": "Folder not found",
"folders": "Folders",
"get_wifiname_error": "Kunne ikke hente Wi-Fi-navn. Sørg for, at du har givet de nødvendige tilladelser og er forbundet til et Wi-Fi-netværk", "get_wifiname_error": "Kunne ikke hente Wi-Fi-navn. Sørg for, at du har givet de nødvendige tilladelser og er forbundet til et Wi-Fi-netværk",
"grant_permission": "Giv tilladelse", "grant_permission": "Giv tilladelse",
"haptic_feedback_switch": "Slå haptisk feedback til", "haptic_feedback_switch": "Slå haptisk feedback til",
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Datum und Uhrzeit bearbeiten", "control_bottom_app_bar_edit_time": "Datum und Uhrzeit bearbeiten",
"control_bottom_app_bar_favorite": "Favorit", "control_bottom_app_bar_favorite": "Favorit",
"control_bottom_app_bar_share": "Teilen", "control_bottom_app_bar_share": "Teilen",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "Teilen mit", "control_bottom_app_bar_share_to": "Teilen mit",
"control_bottom_app_bar_stack": "Stapeln", "control_bottom_app_bar_stack": "Stapeln",
"control_bottom_app_bar_trash_from_immich": "in den Papierkorb schieben", "control_bottom_app_bar_trash_from_immich": "in den Papierkorb schieben",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Zeitzone", "edit_date_time_dialog_timezone": "Zeitzone",
"edit_image_title": "Bearbeiten", "edit_image_title": "Bearbeiten",
"edit_location_dialog_title": "Ort bearbeiten", "edit_location_dialog_title": "Ort bearbeiten",
"empty_folder": "Dieser Ordner ist leer",
"end_date": "Enddatum", "end_date": "Enddatum",
"enqueued": "Eingereiht", "enqueued": "Eingereiht",
"enter_wifi_name": "WLAN-Name eingeben", "enter_wifi_name": "WLAN-Name eingeben",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "Aufnahmeort hinzufügen", "exif_bottom_sheet_location_add": "Aufnahmeort hinzufügen",
"exif_bottom_sheet_people": "PERSONEN", "exif_bottom_sheet_people": "PERSONEN",
"exif_bottom_sheet_person_add_person": "Namen hinzufügen", "exif_bottom_sheet_person_add_person": "Namen hinzufügen",
"exif_bottom_sheet_person_age": "Alter {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "In Arbeit", "experimental_settings_new_asset_list_subtitle": "In Arbeit",
"experimental_settings_new_asset_list_title": "Experimentelles Fotogitter aktivieren", "experimental_settings_new_asset_list_title": "Experimentelles Fotogitter aktivieren",
"experimental_settings_subtitle": "Benutzung auf eigene Gefahr!", "experimental_settings_subtitle": "Benutzung auf eigene Gefahr!",
@@ -276,16 +270,11 @@
"external_network": "Externes Netzwerk", "external_network": "Externes Netzwerk",
"external_network_sheet_info": "Wenn sich die App nicht im bevorzugten WLAN-Netzwerk befindet, verbindet sie sich mit dem Server über die erste der folgenden URLs, die sie erreichen kann (von oben nach unten)", "external_network_sheet_info": "Wenn sich die App nicht im bevorzugten WLAN-Netzwerk befindet, verbindet sie sich mit dem Server über die erste der folgenden URLs, die sie erreichen kann (von oben nach unten)",
"failed": "Fehlgeschlagen", "failed": "Fehlgeschlagen",
"failed_to_load_assets": "Fehler beim Laden von Daten",
"failed_to_load_folder": "Fehler beim Laden des Ordners",
"favorites": "Favoriten", "favorites": "Favoriten",
"favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden", "favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden",
"favorites_page_title": "Favoriten", "favorites_page_title": "Favoriten",
"filename_search": "Dateiname oder Dateityp", "filename_search": "Dateiname oder Dateityp",
"filter": "Filter", "filter": "Filter",
"folder": "Ordner",
"folder_not_found": "Ordner nicht gefunden",
"folders": "Ordner",
"get_wifiname_error": "WLAN-Name konnte nicht ermittelt werden. Vergewissere dich, dass die erforderlichen Berechtigungen erteilt wurden und du mit einem WLAN-Netzwerk verbunden bist.\n", "get_wifiname_error": "WLAN-Name konnte nicht ermittelt werden. Vergewissere dich, dass die erforderlichen Berechtigungen erteilt wurden und du mit einem WLAN-Netzwerk verbunden bist.\n",
"grant_permission": "Erlaubnis gewähren", "grant_permission": "Erlaubnis gewähren",
"haptic_feedback_switch": "Haptisches Feedback aktivieren", "haptic_feedback_switch": "Haptisches Feedback aktivieren",
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Επεξεργασία Ημερομηνίας & Ώρας", "control_bottom_app_bar_edit_time": "Επεξεργασία Ημερομηνίας & Ώρας",
"control_bottom_app_bar_favorite": "Προσθήκη στα αγαπημένα", "control_bottom_app_bar_favorite": "Προσθήκη στα αγαπημένα",
"control_bottom_app_bar_share": "Κοινοποίηση", "control_bottom_app_bar_share": "Κοινοποίηση",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "Κοινοποίηση Σε", "control_bottom_app_bar_share_to": "Κοινοποίηση Σε",
"control_bottom_app_bar_stack": "Στοίβα", "control_bottom_app_bar_stack": "Στοίβα",
"control_bottom_app_bar_trash_from_immich": "Μετακίνηση στα Απορρίμματα", "control_bottom_app_bar_trash_from_immich": "Μετακίνηση στα Απορρίμματα",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Ζώνη ώρας", "edit_date_time_dialog_timezone": "Ζώνη ώρας",
"edit_image_title": "Επεξεργασία", "edit_image_title": "Επεξεργασία",
"edit_location_dialog_title": "Τοποθεσία", "edit_location_dialog_title": "Τοποθεσία",
"empty_folder": "This folder is empty",
"end_date": "End date", "end_date": "End date",
"enqueued": "Enqueued", "enqueued": "Enqueued",
"enter_wifi_name": "Enter WiFi name", "enter_wifi_name": "Enter WiFi name",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "Προσθήκη τοποθεσίας", "exif_bottom_sheet_location_add": "Προσθήκη τοποθεσίας",
"exif_bottom_sheet_people": "ΑΝΘΡΩΠΟΙ", "exif_bottom_sheet_people": "ΑΝΘΡΩΠΟΙ",
"exif_bottom_sheet_person_add_person": "Προσθήκη ονόματος", "exif_bottom_sheet_person_add_person": "Προσθήκη ονόματος",
"exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "Σε εξέλιξη", "experimental_settings_new_asset_list_subtitle": "Σε εξέλιξη",
"experimental_settings_new_asset_list_title": "Ενεργοποίηση πειραματικού πλέγματος φωτογραφιών", "experimental_settings_new_asset_list_title": "Ενεργοποίηση πειραματικού πλέγματος φωτογραφιών",
"experimental_settings_subtitle": "Χρησιμοποιείτε με δική σας ευθύνη!", "experimental_settings_subtitle": "Χρησιμοποιείτε με δική σας ευθύνη!",
@@ -276,16 +270,11 @@
"external_network": "External network", "external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed", "failed": "Failed",
"failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder",
"favorites": "Αγαπημένα", "favorites": "Αγαπημένα",
"favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία", "favorites_page_no_favorites": "Δεν βρέθηκαν αγαπημένα στοιχεία",
"favorites_page_title": "Αγαπημένα", "favorites_page_title": "Αγαπημένα",
"filename_search": "Όνομα αρχείου ή επέκταση", "filename_search": "Όνομα αρχείου ή επέκταση",
"filter": "Φίλτρο", "filter": "Φίλτρο",
"folder": "Folder",
"folder_not_found": "Folder not found",
"folders": "Folders",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission", "grant_permission": "Grant permission",
"haptic_feedback_switch": "Ενεργοποίηση απτικής ανάδρασης", "haptic_feedback_switch": "Ενεργοποίηση απτικής ανάδρασης",
+5 -9
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Edit Date & Time", "control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Favorite", "control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_share": "Share", "control_bottom_app_bar_share": "Share",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_share_to": "Share To",
"control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_trash_from_immich": "Move to Trash", "control_bottom_app_bar_trash_from_immich": "Move to Trash",
@@ -266,9 +265,6 @@
"exif_bottom_sheet_people": "PEOPLE", "exif_bottom_sheet_people": "PEOPLE",
"exif_bottom_sheet_person_add_person": "Add name", "exif_bottom_sheet_person_add_person": "Add name",
"exif_bottom_sheet_person_age": "Age {}", "exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_subtitle": "Work in progress",
"experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!", "experimental_settings_subtitle": "Use at your own risk!",
@@ -276,16 +272,16 @@
"external_network": "External network", "external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed", "failed": "Failed",
"failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder",
"favorites": "Favorites", "favorites": "Favorites",
"favorites_page_no_favorites": "No favorite assets found", "favorites_page_no_favorites": "No favorite assets found",
"favorites_page_title": "Favorites", "favorites_page_title": "Favorites",
"filename_search": "File name or extension", "filename_search": "File name or extension",
"filter": "Filter", "filter": "Filter",
"folder": "Folder",
"folder_not_found": "Folder not found",
"folders": "Folders", "folders": "Folders",
"folder": "Folder",
"failed_to_load_folder": "Failed to load folder",
"failed_to_load_assets": "Failed to load assets",
"folder_not_found": "Folder not found",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission", "grant_permission": "Grant permission",
"haptic_feedback_switch": "Enable haptic feedback", "haptic_feedback_switch": "Enable haptic feedback",
@@ -689,4 +685,4 @@
"viewer_unstack": "Un-Stack", "viewer_unstack": "Un-Stack",
"wifi_name": "WiFi Name", "wifi_name": "WiFi Name",
"your_wifi_name": "Your WiFi name" "your_wifi_name": "Your WiFi name"
} }
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Editar fecha y hora", "control_bottom_app_bar_edit_time": "Editar fecha y hora",
"control_bottom_app_bar_favorite": "Favorito", "control_bottom_app_bar_favorite": "Favorito",
"control_bottom_app_bar_share": "Compartir", "control_bottom_app_bar_share": "Compartir",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "Enviar", "control_bottom_app_bar_share_to": "Enviar",
"control_bottom_app_bar_stack": "Apilar", "control_bottom_app_bar_stack": "Apilar",
"control_bottom_app_bar_trash_from_immich": "Mover a la papelera", "control_bottom_app_bar_trash_from_immich": "Mover a la papelera",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Zona horaria", "edit_date_time_dialog_timezone": "Zona horaria",
"edit_image_title": "Editar", "edit_image_title": "Editar",
"edit_location_dialog_title": "Ubicación", "edit_location_dialog_title": "Ubicación",
"empty_folder": "This folder is empty",
"end_date": "End date", "end_date": "End date",
"enqueued": "Enqueued", "enqueued": "Enqueued",
"enter_wifi_name": "Enter WiFi name", "enter_wifi_name": "Enter WiFi name",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "Añadir ubicación", "exif_bottom_sheet_location_add": "Añadir ubicación",
"exif_bottom_sheet_people": "PERSONAS", "exif_bottom_sheet_people": "PERSONAS",
"exif_bottom_sheet_person_add_person": "Añadir nombre", "exif_bottom_sheet_person_add_person": "Añadir nombre",
"exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso", "experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
"experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental", "experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental",
"experimental_settings_subtitle": "Úsalo bajo tu responsabilidad", "experimental_settings_subtitle": "Úsalo bajo tu responsabilidad",
@@ -276,16 +270,11 @@
"external_network": "External network", "external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed", "failed": "Failed",
"failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder",
"favorites": "Favoritos", "favorites": "Favoritos",
"favorites_page_no_favorites": "No se encontraron elementos marcados como favoritos", "favorites_page_no_favorites": "No se encontraron elementos marcados como favoritos",
"favorites_page_title": "Favoritos", "favorites_page_title": "Favoritos",
"filename_search": "Nombre o extensión", "filename_search": "Nombre o extensión",
"filter": "Filtrar", "filter": "Filtrar",
"folder": "Folder",
"folder_not_found": "Folder not found",
"folders": "Folders",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission", "grant_permission": "Grant permission",
"haptic_feedback_switch": "Activar respuesta háptica", "haptic_feedback_switch": "Activar respuesta háptica",
-11
View File
@@ -197,7 +197,6 @@
"control_bottom_app_bar_edit_time": "Edit Date & Time", "control_bottom_app_bar_edit_time": "Edit Date & Time",
"control_bottom_app_bar_favorite": "Favorito", "control_bottom_app_bar_favorite": "Favorito",
"control_bottom_app_bar_share": "Compartir", "control_bottom_app_bar_share": "Compartir",
"control_bottom_app_bar_share_link": "Share Link",
"control_bottom_app_bar_share_to": "Enviar", "control_bottom_app_bar_share_to": "Enviar",
"control_bottom_app_bar_stack": "Apilar", "control_bottom_app_bar_stack": "Apilar",
"control_bottom_app_bar_trash_from_immich": "Move to Trash", "control_bottom_app_bar_trash_from_immich": "Move to Trash",
@@ -253,7 +252,6 @@
"edit_date_time_dialog_timezone": "Timezone", "edit_date_time_dialog_timezone": "Timezone",
"edit_image_title": "Edit", "edit_image_title": "Edit",
"edit_location_dialog_title": "Location", "edit_location_dialog_title": "Location",
"empty_folder": "This folder is empty",
"end_date": "End date", "end_date": "End date",
"enqueued": "Enqueued", "enqueued": "Enqueued",
"enter_wifi_name": "Enter WiFi name", "enter_wifi_name": "Enter WiFi name",
@@ -265,10 +263,6 @@
"exif_bottom_sheet_location_add": "Add a location", "exif_bottom_sheet_location_add": "Add a location",
"exif_bottom_sheet_people": "PERSONAS", "exif_bottom_sheet_people": "PERSONAS",
"exif_bottom_sheet_person_add_person": "Add name", "exif_bottom_sheet_person_add_person": "Add name",
"exif_bottom_sheet_person_age": "Age {}",
"exif_bottom_sheet_person_age_months": "Age {} months",
"exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months",
"exif_bottom_sheet_person_age_years": "Age {}",
"experimental_settings_new_asset_list_subtitle": "Trabajo en progreso", "experimental_settings_new_asset_list_subtitle": "Trabajo en progreso",
"experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental", "experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotográfica experimental",
"experimental_settings_subtitle": "Úsalo bajo tu responsabilidad", "experimental_settings_subtitle": "Úsalo bajo tu responsabilidad",
@@ -276,16 +270,11 @@
"external_network": "External network", "external_network": "External network",
"external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", "external_network_sheet_info": "When not on the preferred WiFi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom",
"failed": "Failed", "failed": "Failed",
"failed_to_load_assets": "Failed to load assets",
"failed_to_load_folder": "Failed to load folder",
"favorites": "Favoritos", "favorites": "Favoritos",
"favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos", "favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos",
"favorites_page_title": "Favoritos", "favorites_page_title": "Favoritos",
"filename_search": "File name or extension", "filename_search": "File name or extension",
"filter": "Filter", "filter": "Filter",
"folder": "Folder",
"folder_not_found": "Folder not found",
"folders": "Folders",
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
"grant_permission": "Grant permission", "grant_permission": "Grant permission",
"haptic_feedback_switch": "Enable haptic feedback", "haptic_feedback_switch": "Enable haptic feedback",

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