diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml
index 71b57ecb2d..e423c2260b 100644
--- a/.github/workflows/build-mobile.yml
+++ b/.github/workflows/build-mobile.yml
@@ -103,14 +103,14 @@ jobs:
working-directory: ./mobile
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
- - uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
+ - uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
distribution: 'zulu'
java-version: '17'
- name: Restore Gradle Cache
id: cache-gradle-restore
- uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
~/.gradle/caches
@@ -181,7 +181,7 @@ jobs:
- name: Save Gradle Cache
id: cache-gradle-save
- uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
if: github.ref == 'refs/heads/main'
with:
path: |
@@ -237,7 +237,7 @@ jobs:
run: flutter build ios --config-only --no-codesign
- name: Setup Ruby
- uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1.313.0
+ uses: ruby/setup-ruby@0dafeac902942906541bc140009cdbf32665b601 # v1.315.0
with:
ruby-version: '3.3'
bundler-cache: true
diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml
index 9a93e931d3..51d56aa0a7 100644
--- a/.github/workflows/check-openapi.yml
+++ b/.github/workflows/check-openapi.yml
@@ -25,7 +25,7 @@ jobs:
persist-credentials: false
- name: Check for breaking API changes
- uses: oasdiff/oasdiff-action/breaking@e24529087d93f837b28b50bb66ba9016380a7fcc # v0.1.2
+ uses: oasdiff/oasdiff-action/breaking@ae5ef6402ebe218ac6e872c59d0fa4557694e75c # v0.1.4
with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json
diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 821427aef5..890e525815 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -75,13 +75,13 @@ jobs:
token: ${{ steps.token.outputs.token }}
- name: Set up QEMU
- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
+ uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
+ uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to GitHub Container Registry
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
+ uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
@@ -96,7 +96,7 @@ jobs:
- name: Generate docker image tags
id: metadata
- uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
flavor: |
latest=false
@@ -107,7 +107,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}
- name: Build and push image
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
file: packages/cli/Dockerfile
platforms: linux/amd64,linux/arm64
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 856be26a35..2b86da5948 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -57,7 +57,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
+ uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +70,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
+ uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +83,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
+ uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
category: '/language:${{matrix.language}}'
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 9ff2a705e6..40748820aa 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -60,7 +60,7 @@ jobs:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps:
- name: Login to GitHub Container Registry
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
+ uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -90,7 +90,7 @@ jobs:
suffix: ['']
steps:
- name: Login to GitHub Container Registry
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
+ uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 35b3249855..016032758b 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -149,7 +149,7 @@ jobs:
github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release
- uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
+ uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
draft: true
prerelease: ${{ needs.bump_version.outputs.rc }}
diff --git a/README.md b/README.md
index 7e06d9de4b..13c4009b68 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@
العربية
Tiếng Việt
ภาษาไทย
+ മലയാളം
diff --git a/deployment/mise.lock b/deployment/mise.lock
index fb7be27b99..663ae4985d 100644
--- a/deployment/mise.lock
+++ b/deployment/mise.lock
@@ -7,30 +7,37 @@ backend = "aqua:opentofu/opentofu"
[tools.opentofu."platforms.linux-arm64"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
[tools.opentofu."platforms.linux-arm64-musl"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
[tools.opentofu."platforms.linux-x64"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
[tools.opentofu."platforms.linux-x64-musl"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
[tools.opentofu."platforms.macos-arm64"]
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399"
[tools.opentofu."platforms.macos-x64"]
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384"
[tools.opentofu."platforms.windows-x64"]
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
[[tools.terragrunt]]
version = "1.0.3"
@@ -39,27 +46,34 @@ backend = "aqua:gruntwork-io/terragrunt"
[tools.terragrunt."platforms.linux-arm64"]
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
[tools.terragrunt."platforms.linux-arm64-musl"]
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
[tools.terragrunt."platforms.linux-x64"]
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
[tools.terragrunt."platforms.linux-x64-musl"]
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
[tools.terragrunt."platforms.macos-arm64"]
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084"
[tools.terragrunt."platforms.macos-x64"]
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970"
[tools.terragrunt."platforms.windows-x64"]
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412"
diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md
index debbad7575..df34671a1b 100644
--- a/docs/docs/administration/backup-and-restore.md
+++ b/docs/docs/administration/backup-and-restore.md
@@ -31,7 +31,7 @@ You can trigger a database backup manually:
1. Go to **Administration > Job Queues**
2. Click **Create job** in the top right
-3. Select **Create Database Backup** and click **Confirm**
+3. Select **Create Database Dump** and click **Confirm**
The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit.
diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md
index 167fa10320..8789d0c4e9 100644
--- a/docs/docs/developer/setup.md
+++ b/docs/docs/developer/setup.md
@@ -30,7 +30,7 @@ This environment includes the services below. Additional details are available i
- Redis
- PostgreSQL development database with exposed port `5432` so you can use any database client to access it
-All the services are packaged to run as with single Docker Compose command.
+All the services are packaged to run with a single Docker Compose command.
:::tip mise
[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`.
@@ -41,7 +41,7 @@ All the services are packaged to run as with single Docker Compose command.
1. Clone the project repo.
2. Run `cp docker/example.env docker/.env`.
3. Edit `docker/.env` to provide values for the required variable `UPLOAD_LOCATION`.
-4. Install dependencies - `pnpm i`
+4. Install dependencies - `mise x -- pnpm i`
5. From the root directory, run:
```bash title="Start development server"
@@ -52,7 +52,7 @@ mise dev
All the services will be started with hot-reloading enabled for a quick feedback loop.
-You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000/api`
+You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000`
**Notes:**
diff --git a/docs/docs/install/upgrading.md b/docs/docs/install/upgrading.md
index 8fc9113e6d..f302719c76 100644
--- a/docs/docs/install/upgrading.md
+++ b/docs/docs/install/upgrading.md
@@ -28,11 +28,15 @@ docker image prune
## Versioning Policy
-Immich follows [semantic versioning][semver], which tags releases in the format `..`. We intend for breaking changes to be limited to major version releases.
-You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`.
+Immich follows [semantic versioning][semver], which tags releases in the format `..`.
+We intend for breaking changes, including those to the API or deployment, to be limited to major version releases.
+You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`. These metatags do not follow release candidates.
-Currently, we have no plans to backport patches to earlier versions. We encourage all users to run the most recent release of Immich.
-Switching back to an earlier version, even within the same minor release tag, is not supported.
+The mobile app is typically compatible with the current and prior major version. However, the server is only compatible with the matching major version.
+Thus, we recommend upgrading all mobile clients before upgrading the server to ensure compatibility.
+
+We do not backport patches to earlier versions. We encourage all users to run the most recent stable release of Immich.
+Downgrading to an earlier version, even within the same minor version, is not supported.
[semver]: https://semver.org/
diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json
index aa52b78032..5f9f727219 100644
--- a/docs/static/archived-versions.json
+++ b/docs/static/archived-versions.json
@@ -1,7 +1,7 @@
[
{
- "label": "v3.0.0",
- "url": "https://docs.v3.0.0.archive.immich.app"
+ "label": "v3.0.1",
+ "url": "https://docs.v3.0.1.archive.immich.app"
},
{
"label": "v2.7.5",
diff --git a/e2e/package.json b/e2e/package.json
index 55ab61d0e6..a89c00c5e4 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
- "version": "3.0.0",
+ "version": "3.0.1",
"description": "",
"main": "index.js",
"type": "module",
diff --git a/e2e/src/specs/server/api/search.e2e-spec.ts b/e2e/src/specs/server/api/search.e2e-spec.ts
index 0b86053f78..753a23d442 100644
--- a/e2e/src/specs/server/api/search.e2e-spec.ts
+++ b/e2e/src/specs/server/api/search.e2e-spec.ts
@@ -4,6 +4,7 @@ import {
AssetVisibility,
deleteAssets,
LoginResponseDto,
+ SharedLinkType,
updateAsset,
} from '@immich/sdk';
import { DateTime } from 'luxon';
@@ -357,6 +358,32 @@ describe('/search', () => {
expect(body.assets.items).toHaveLength(assets.length);
});
}
+
+ it('should reject shared link access without an album filter', async () => {
+ const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo' });
+ const sharedLink = await utils.createSharedLink(admin.accessToken, {
+ type: SharedLinkType.Album,
+ albumId: album.id,
+ });
+ const { status, body } = await request(app).post(`/search/metadata?key=${sharedLink.key}`).send({});
+ expect(status).toBe(400);
+ expect(body).toEqual({ message: 'Shared link access is only allowed in combination with an albumIds filter' });
+ });
+
+ it('should allow shared link access for albums', async () => {
+ const asset = await utils.createAsset(admin.accessToken);
+ const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo', assetIds: [asset.id] });
+ const sharedLink = await utils.createSharedLink(admin.accessToken, {
+ type: SharedLinkType.Album,
+ albumId: album.id,
+ });
+
+ const { status, body } = await request(app)
+ .post(`/search/metadata?key=${sharedLink.key}`)
+ .send({ albumIds: [album.id] });
+ expect(status).toBe(200);
+ expect(body.assets.items).toEqual([expect.objectContaining({ id: asset.id })]);
+ });
});
describe('POST /search/random', () => {
diff --git a/i18n/en.json b/i18n/en.json
index 45951c6ffe..5f8509b4c7 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -431,6 +431,10 @@
"transcoding_realtime_description": "Allows transcoding to be performed in real-time as the video is being streamed. Enables quality switching, but may cause higher playback latency and stuttering depending on server capabilities.",
"transcoding_realtime_enabled": "Enable real-time transcoding",
"transcoding_realtime_enabled_description": "If disabled, the server will refuse to start new real-time transcoding sessions.",
+ "transcoding_realtime_resolutions": "Resolutions",
+ "transcoding_realtime_resolutions_description": "The resolutions offered for real-time transcoding. Higher resolutions may cause playback issues if the server cannot transcode them quickly enough.",
+ "transcoding_realtime_video_codecs": "Video codecs",
+ "transcoding_realtime_video_codecs_description": "The video codecs offered for real-time transcoding. Clients will choose the best option they support during playback. AV1 is more efficient than HEVC, which is more efficient than H.264. When using hardware acceleration, only select the codecs the accelerator can encode. When using software transcoding, note that H.264 is faster than AV1, which is faster than HEVC.",
"transcoding_reference_frames": "Reference frames",
"transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.",
"transcoding_required_description": "Only videos not in an accepted format",
@@ -593,6 +597,7 @@
"asset_viewer_settings_title": "Asset Viewer",
"assets": "Assets",
"assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album",
+ "assets_added_to_album_partial_count": "Added {successCount} out of {totalCount} {totalCount, plural, one {asset} other {assets}} to the album",
"assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}",
"assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album",
"assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums",
@@ -2150,6 +2155,7 @@
"unknown": "Unknown",
"unknown_country": "Unknown Country",
"unknown_date": "Unknown date",
+ "unknown_schema": "Unknown schema",
"unknown_year": "Unknown Year",
"unlimited": "Unlimited",
"unlink_motion_video": "Unlink motion video",
diff --git a/machine-learning/mise.lock b/machine-learning/mise.lock
index e366eaa4be..560a5bcbd3 100644
--- a/machine-learning/mise.lock
+++ b/machine-learning/mise.lock
@@ -5,38 +5,38 @@ version = "3.11.15"
backend = "core:python"
[tools.python."platforms.linux-arm64"]
-checksum = "sha256:cbce0660e88cd9c56be7aaf2a2df92bea51f359388a521838b6b01817d728df0"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
+checksum = "sha256:9ac18c9a761e91e6c6452bc0ef0082922a00a3fdec734555635d57c3169309b7"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
provenance = "github-attestations"
[tools.python."platforms.linux-arm64-musl"]
-checksum = "sha256:a55ea44225ee3741d4157c383f3d5c3e8eee5f9665e2ea069233486b4275d928"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-unknown-linux-musl-install_only_stripped.tar.gz"
+checksum = "sha256:bcd414f2e9d8cc5d4ac7fab2c8f912e30379c150fa6b94da19d64daf85cfc5d4"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-unknown-linux-musl-install_only_stripped.tar.gz"
provenance = "github-attestations"
[tools.python."platforms.linux-x64"]
-checksum = "sha256:67a5b22f796e96f4d7fa628f95866d5fd1d524d0588f74e4601facd82b66792b"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
+checksum = "sha256:0604cd029b142dc223e131f17f5941c0c8d2d5074997c8178b515b19eea2a6c2"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
provenance = "github-attestations"
[tools.python."platforms.linux-x64-musl"]
-checksum = "sha256:5a8544aa4303da3ca4b7505c98dd8453b671157039d25cd551e55abea0f83a60"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-unknown-linux-musl-install_only_stripped.tar.gz"
+checksum = "sha256:45bffdb320cf4a2c2dbf183b634aeb7e28f9cb0646f715b2f4117c11084d839a"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-unknown-linux-musl-install_only_stripped.tar.gz"
provenance = "github-attestations"
[tools.python."platforms.macos-arm64"]
-checksum = "sha256:8c56f1f59142e0f9f8861ad897bdfd97fd84403afa7b3d8b0f33b208ec471355"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-aarch64-apple-darwin-install_only_stripped.tar.gz"
+checksum = "sha256:2318799eaf104f8a29bc09a93b0851b05dbbcb4ce9a5f045ddea169c0c7ff3a5"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-apple-darwin-install_only_stripped.tar.gz"
provenance = "github-attestations"
[tools.python."platforms.macos-x64"]
-checksum = "sha256:8cd3878c656ba1698314cbcb65f78df4c37b7c8eabff958558115c6db11adb3d"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-apple-darwin-install_only_stripped.tar.gz"
+checksum = "sha256:4925e5aaa9bc77c85302d350b36c1d9def2002996a6bcfa55c88ba6eb318de29"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-apple-darwin-install_only_stripped.tar.gz"
provenance = "github-attestations"
[tools.python."platforms.windows-x64"]
-checksum = "sha256:f081a733b4e7ba0e5e5e12d533b3c795dbef3ecbebf92f0b4202e5329bf7c8ab"
-url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260610/cpython-3.11.15+20260610-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
+checksum = "sha256:6589ca6d63f520bec4096d62b3ab91da3d0a80b16b594c99a6b677e335814683"
+url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
provenance = "github-attestations"
[[tools.uv]]
@@ -46,27 +46,34 @@ backend = "aqua:astral-sh/uv"
[tools.uv."platforms.linux-arm64"]
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235419"
[tools.uv."platforms.linux-arm64-musl"]
checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235419"
[tools.uv."platforms.linux-x64"]
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235475"
[tools.uv."platforms.linux-x64-musl"]
checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235475"
[tools.uv."platforms.macos-arm64"]
checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235412"
[tools.uv."platforms.macos-x64"]
checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235464"
[tools.uv."platforms.windows-x64"]
checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705"
url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip"
+url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235467"
diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml
index 70c137a354..fe22b91a14 100644
--- a/machine-learning/pyproject.toml
+++ b/machine-learning/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "immich-ml"
-version = "3.0.0"
+version = "3.0.1"
description = ""
authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }]
requires-python = ">=3.11,<4.0"
diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock
index 5d84196dc9..8e081c12b4 100644
--- a/machine-learning/uv.lock
+++ b/machine-learning/uv.lock
@@ -974,7 +974,7 @@ wheels = [
[[package]]
name = "immich-ml"
-version = "3.0.0"
+version = "3.0.1"
source = { editable = "." }
dependencies = [
{ name = "aiocache" },
diff --git a/mise.lock b/mise.lock
index 44ecd6fdbe..f12064a23c 100644
--- a/mise.lock
+++ b/mise.lock
@@ -254,30 +254,37 @@ backend = "aqua:opentofu/opentofu"
[tools.opentofu."platforms.linux-arm64"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
[tools.opentofu."platforms.linux-arm64-musl"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
[tools.opentofu."platforms.linux-x64"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
[tools.opentofu."platforms.linux-x64-musl"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
[tools.opentofu."platforms.macos-arm64"]
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399"
[tools.opentofu."platforms.macos-x64"]
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384"
[tools.opentofu."platforms.windows-x64"]
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
+url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
[[tools.pnpm]]
version = "11.6.0"
@@ -286,31 +293,37 @@ backend = "aqua:pnpm/pnpm"
[tools.pnpm."platforms.linux-arm64"]
checksum = "sha256:2fec653ff6dadab340d1c3d2214688a7451cc471f39710839440b293ca7c53b0"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64.tar.gz"
+url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120174"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-arm64-musl"]
checksum = "sha256:56a78c08cf22adf29e7dacb6f7f100139731693863d20fb94a7883463a62169e"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-arm64-musl.tar.gz"
+url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120177"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64"]
checksum = "sha256:74d64c1646385fb21691f32f0ab6aca1a9f5c829ba54d3cda3a24838a228e68c"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64.tar.gz"
+url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120176"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64-musl"]
checksum = "sha256:7a0c463a09d912fba6b7d9eca0a946bc228ea50f3015a05c09a29e7e85a932d7"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-linux-x64-musl.tar.gz"
+url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120175"
provenance = "github-attestations"
[tools.pnpm."platforms.macos-arm64"]
checksum = "sha256:87c901635a14481fb30566a3749041134ffd4317bc6fe866c345b69fdf9b6b85"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-darwin-arm64.tar.gz"
+url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120180"
provenance = "github-attestations"
[tools.pnpm."platforms.windows-x64"]
checksum = "sha256:91c753435542b04859c689304fae0dd64eba6b40937cfa426a48485b712e4e9c"
url = "https://github.com/pnpm/pnpm/releases/download/v11.6.0/pnpm-win32-x64.zip"
+url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/445120173"
provenance = "github-attestations"
[[tools.terragrunt]]
@@ -320,27 +333,34 @@ backend = "aqua:gruntwork-io/terragrunt"
[tools.terragrunt."platforms.linux-arm64"]
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
[tools.terragrunt."platforms.linux-arm64-musl"]
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
[tools.terragrunt."platforms.linux-x64"]
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
[tools.terragrunt."platforms.linux-x64-musl"]
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
[tools.terragrunt."platforms.macos-arm64"]
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084"
[tools.terragrunt."platforms.macos-x64"]
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970"
[tools.terragrunt."platforms.windows-x64"]
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
+url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412"
diff --git a/mobile/android/app/src/main/cpp/native_image.c b/mobile/android/app/src/main/cpp/native_image.c
index 07bb7e9a2c..3a4f2f11a4 100644
--- a/mobile/android/app/src/main/cpp/native_image.c
+++ b/mobile/android/app/src/main/cpp/native_image.c
@@ -107,3 +107,67 @@ Java_app_alextran_immich_NativeImage_rotate(
}
return (jlong) dst;
}
+
+// Convert an RGBA_1010102 buffer to densely-packed RGBA_8888, matching Skia's
+// Bitmap.copy(ARGB_8888) byte-for-byte so it's a drop-in for the intermediate 8888 bitmap.
+// Each source pixel is a native (little-endian on every Android ABI) u32 with R in bits 0-9,
+// G in 10-19, B in 20-29, A in 30-31 (standard RGB10_A2). Each 10-bit channel maps to 8-bit via
+// round(v*255/1023). The 2-bit alpha maps to a*85. Both are kept as plain arithmetic as the whole
+// loop auto-vectorizes to NEON and measures faster than a LUT on-device. Output is R,G,B,A bytes
+// per pixel, i.e. Android ARGB_8888 memory == Dart PixelFormat.rgba8888.
+static void convert_1010102(const uint8_t *src, int srcStride, uint32_t *dst, int w, int h) {
+ for (int y = 0; y < h; y++) {
+ const uint32_t *srcRow = (const uint32_t *) (src + (size_t) y * srcStride);
+ uint32_t *dstRow = dst + (size_t) y * w;
+ for (int x = 0; x < w; x++) {
+ uint32_t px = srcRow[x];
+ uint32_t r = ((px & 0x3FF) * 16336u + 32768u) >> 16;
+ uint32_t g = (((px >> 10) & 0x3FF) * 16336u + 32768u) >> 16;
+ uint32_t b = (((px >> 20) & 0x3FF) * 16336u + 32768u) >> 16;
+ uint32_t a = ((px >> 30) & 0x3) * 85u;
+ dstRow[x] = r | (g << 8) | (b << 16) | (a << 24);
+ }
+ }
+}
+
+// Converts an RGBA_1010102 bitmap (what a 10-bit HEIC/AVIF decodes to on API 33+) into a freshly
+// malloc'd RGBA_8888 buffer. Fills outInfo with {width, height, rowBytes} and returns the buffer
+// address, or 0 (so the caller falls back to a Skia copy) if the bitmap isn't 1010102 or can't be
+// locked. Same ownership contract as rotate: free the returned buffer via NativeBuffer.free.
+JNIEXPORT jlong JNICALL
+Java_app_alextran_immich_NativeImage_convert1010102(
+ JNIEnv *env, jclass clazz, jobject bitmap, jintArray outInfo) {
+ AndroidBitmapInfo info;
+ if (AndroidBitmap_getInfo(env, bitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) {
+ return 0;
+ }
+ if (info.format != ANDROID_BITMAP_FORMAT_RGBA_1010102) {
+ return 0;
+ }
+
+ int w = (int) info.width;
+ int h = (int) info.height;
+
+ uint32_t *dst = (uint32_t *) malloc((size_t) w * h * 4);
+ if (dst == NULL) {
+ return 0;
+ }
+
+ void *srcPixels = NULL;
+ if (AndroidBitmap_lockPixels(env, bitmap, &srcPixels) != ANDROID_BITMAP_RESULT_SUCCESS) {
+ free(dst);
+ return 0;
+ }
+
+ convert_1010102((const uint8_t *) srcPixels, (int) info.stride, dst, w, h);
+
+ AndroidBitmap_unlockPixels(env, bitmap);
+
+ jint dims[3] = {w, h, w * 4};
+ (*env)->SetIntArrayRegion(env, outInfo, 0, 3, dims);
+ if ((*env)->ExceptionCheck(env)) {
+ free(dst);
+ return 0;
+ }
+ return (jlong) dst;
+}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt
index b1398d84b8..68feaa2d14 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt
@@ -16,4 +16,14 @@ object NativeImage {
*/
@JvmStatic
external fun rotate(bitmap: Bitmap, orientation: Int, outInfo: IntArray): Long
+
+ /**
+ * Converts an RGBA_1010102 [bitmap] (what a 10-bit HEIC/AVIF decodes to on API 33+) to RGBA_8888,
+ * writing the result into a freshly malloc'd native buffer in one pass, with no intermediate
+ * ARGB_8888 bitmap. Returns the buffer address (free it with [NativeBuffer.free]) and fills
+ * [outInfo] with {width, height, rowBytes}. Returns 0 when the bitmap isn't RGBA_1010102 so the
+ * caller can fall back to a Skia copy.
+ */
+ @JvmStatic
+ external fun convert1010102(bitmap: Bitmap, outInfo: IntArray): Long
}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt
index 6d2499ee0f..2f5db1ca0e 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt
@@ -46,22 +46,47 @@ inline fun ImageDecoder.Source.decodeBitmap(target: Size = Size(0, 0)): Bitmap {
}
fun Bitmap.toNativeBuffer(): Map {
- val size = width * height * 4
+ // Dart reads the buffer as rgba8888, but 10-bit sources decode to RGBA_1010102, which garbles
+ // colors when copied verbatim. Convert those straight into the output buffer in native code -
+ // one pass, no intermediate ARGB_8888 bitmap.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) {
+ val info = IntArray(3)
+ val pointer = NativeImage.convert1010102(this, info)
+ if (pointer != 0L) {
+ recycle()
+ return mapOf(
+ "pointer" to pointer,
+ "width" to info[0].toLong(),
+ "height" to info[1].toLong(),
+ "rowBytes" to info[2].toLong()
+ )
+ }
+ // native convert declined (OOM/lock) -> fall through to the Skia copy path below.
+ }
+ // Other non-8888 configs (e.g. HDR F16) still need Skia's convert; 8-bit is copied as-is.
+ val bitmap = if (config != Bitmap.Config.ARGB_8888) {
+ val converted = copy(Bitmap.Config.ARGB_8888, false)
+ recycle()
+ converted ?: throw IOException("could not convert bitmap to ARGB_8888")
+ } else {
+ this
+ }
+ val size = bitmap.width * bitmap.height * 4
val pointer = NativeBuffer.allocate(size)
try {
val buffer = NativeBuffer.wrap(pointer, size)
- copyPixelsToBuffer(buffer)
+ bitmap.copyPixelsToBuffer(buffer)
return mapOf(
"pointer" to pointer,
- "width" to width.toLong(),
- "height" to height.toLong(),
- "rowBytes" to (width * 4).toLong()
+ "width" to bitmap.width.toLong(),
+ "height" to bitmap.height.toLong(),
+ "rowBytes" to (bitmap.width * 4).toLong()
)
- } catch (e: Exception) {
+ } catch (e: Throwable) {
NativeBuffer.free(pointer)
throw e
} finally {
- recycle()
+ bitmap.recycle()
}
}
diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt
index f7ebc349f6..226305dd85 100644
--- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt
+++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt
@@ -1,6 +1,8 @@
package app.alextran.immich.images
import android.content.Context
+import android.graphics.ImageDecoder
+import android.os.Build
import android.os.CancellationSignal
import android.os.OperationCanceledException
import app.alextran.immich.INITIAL_BUFFER_SIZE
@@ -22,6 +24,7 @@ import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.Executors
private const val MAX_PREALLOC_BYTES = 128 * 1024 * 1024
@@ -36,12 +39,16 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
companion object {
val CANCELLED = Result.success