From 1e184a70f1cae695db7a69482dd178c11a70f4b9 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Mon, 17 Mar 2025 19:18:22 +0530 Subject: [PATCH 01/18] refactor: cleanup background service (#16855) refactor: background service Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- mobile/lib/services/background.service.dart | 146 +++++--------------- 1 file changed, 33 insertions(+), 113 deletions(-) diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart index d69f282103..2d63e1fc18 100644 --- a/mobile/lib/services/background.service.dart +++ b/mobile/lib/services/background.service.dart @@ -11,57 +11,32 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; -import 'package:immich_mobile/domain/interfaces/user.interface.dart'; -import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; -import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; import 'package:immich_mobile/models/backup/success_upload_asset.model.dart'; -import 'package:immich_mobile/repositories/album.repository.dart'; -import 'package:immich_mobile/repositories/album_api.repository.dart'; -import 'package:immich_mobile/repositories/album_media.repository.dart'; -import 'package:immich_mobile/repositories/asset.repository.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/auth.repository.dart'; -import 'package:immich_mobile/repositories/auth_api.repository.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; -import 'package:immich_mobile/repositories/etag.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; -import 'package:immich_mobile/repositories/network.repository.dart'; -import 'package:immich_mobile/repositories/partner.repository.dart'; -import 'package:immich_mobile/repositories/partner_api.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; -import 'package:immich_mobile/services/album.service.dart'; -import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/backup.service.dart'; -import 'package:immich_mobile/services/entity.service.dart'; -import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; -import 'package:immich_mobile/services/network.service.dart'; -import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/http_ssl_cert_override.dart'; -import 'package:network_info_plus/network_info_plus.dart'; import 'package:path_provider_foundation/path_provider_foundation.dart'; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; -final backgroundServiceProvider = Provider( - (ref) => BackgroundService(), -); +final backgroundServiceProvider = Provider((ref) => BackgroundService()); /// Background backup service class BackgroundService { @@ -377,96 +352,40 @@ class BackgroundService { final db = await Bootstrap.initIsar(); await Bootstrap.initDomain(db); + final ref = ProviderContainer( + overrides: [ + dbProvider.overrideWithValue(db), + isarProvider.overrideWithValue(db), + ], + ); + HttpOverrides.global = HttpSSLCertOverride(); - ApiService apiService = ApiService(); - apiService.setAccessToken(Store.get(StoreKey.accessToken)); - AppSettingsService settingsService = AppSettingsService(); - AlbumRepository albumRepository = AlbumRepository(db); - AssetRepository assetRepository = AssetRepository(db); - BackupAlbumRepository backupRepository = BackupAlbumRepository(db); - IExifInfoRepository exifInfoRepository = IsarExifRepository(db); - ETagRepository eTagRepository = ETagRepository(db); - AlbumMediaRepository albumMediaRepository = AlbumMediaRepository(); - FileMediaRepository fileMediaRepository = FileMediaRepository(); - AssetMediaRepository assetMediaRepository = AssetMediaRepository(); - IUserRepository userRepository = IsarUserRepository(db); - IUserApiRepository userApiRepository = - UserApiRepository(apiService.usersApi); - AlbumApiRepository albumApiRepository = - AlbumApiRepository(apiService.albumsApi); - PartnerApiRepository partnerApiRepository = - PartnerApiRepository(apiService.partnersApi); - HashService hashService = - HashService(assetRepository, this, albumMediaRepository); - EntityService entityService = - EntityService(assetRepository, userRepository); - IPartnerRepository partnerRepository = PartnerRepository(db); - SyncService syncSerive = SyncService( - hashService, - entityService, - albumMediaRepository, - albumApiRepository, - albumRepository, - assetRepository, - exifInfoRepository, - partnerRepository, - userRepository, - StoreService.I, - eTagRepository, - partnerApiRepository, - userApiRepository, - ); - AlbumService albumService = AlbumService( - syncSerive, - entityService, - albumRepository, - assetRepository, - backupRepository, - albumMediaRepository, - albumApiRepository, - ); - BackupService backupService = BackupService( - apiService, - settingsService, - albumService, - albumMediaRepository, - fileMediaRepository, - assetRepository, - assetMediaRepository, - ); - - AuthApiRepository authApiRepository = AuthApiRepository(apiService); - AuthRepository authRepository = AuthRepository(db); - NetworkRepository networkRepository = NetworkRepository(NetworkInfo()); - PermissionRepository permissionRepository = PermissionRepository(); - NetworkService networkService = - NetworkService(networkRepository, permissionRepository); - AuthService authService = AuthService( - authApiRepository, - authRepository, - apiService, - networkService, - ); - - final endpoint = await authService.setOpenApiServiceEndpoint(); + ref + .read(apiServiceProvider) + .setAccessToken(Store.get(StoreKey.accessToken)); + await ref.read(authServiceProvider).setOpenApiServiceEndpoint(); if (kDebugMode) { - debugPrint("[BG UPLOAD] Using endpoint: $endpoint"); + debugPrint( + "[BG UPLOAD] Using endpoint: ${ref.read(apiServiceProvider).apiClient.basePath}", + ); } - final selectedAlbums = - await backupRepository.getAllBySelection(BackupSelection.select); - final excludedAlbums = - await backupRepository.getAllBySelection(BackupSelection.exclude); + final selectedAlbums = await ref + .read(backupAlbumRepositoryProvider) + .getAllBySelection(BackupSelection.select); + final excludedAlbums = await ref + .read(backupAlbumRepositoryProvider) + .getAllBySelection(BackupSelection.exclude); if (selectedAlbums.isEmpty) { return true; } - await fileMediaRepository.enableBackgroundAccess(); + await ref.read(fileMediaRepositoryProvider).enableBackgroundAccess(); do { final bool backupOk = await _runBackup( - backupService, - settingsService, + ref.read(backupServiceProvider), + ref.read(appSettingsServiceProvider), selectedAlbums, excludedAlbums, ); @@ -475,8 +394,9 @@ class BackgroundService { final backupAlbums = [...selectedAlbums, ...excludedAlbums]; backupAlbums.sortBy((e) => e.id); - final dbAlbums = - await backupRepository.getAll(sort: BackupAlbumSort.id); + final dbAlbums = await ref + .read(backupAlbumRepositoryProvider) + .getAll(sort: BackupAlbumSort.id); final List toDelete = []; final List toUpsert = []; // stores the most recent `lastBackup` per album but always keeps the `selection` from the most recent DB state @@ -494,8 +414,8 @@ class BackgroundService { onlyFirst: (BackupAlbum a) => toUpsert.add(a), onlySecond: (BackupAlbum b) => toDelete.add(b.isarId), ); - await backupRepository.deleteAll(toDelete); - await backupRepository.updateAll(toUpsert); + await ref.read(backupAlbumRepositoryProvider).deleteAll(toDelete); + await ref.read(backupAlbumRepositoryProvider).updateAll(toUpsert); } else if (Store.tryGet(StoreKey.backupFailedSince) == null) { Store.put(StoreKey.backupFailedSince, DateTime.now()); return false; From 14c3b99c0f97cf60dd7defc0e3293d53c7bc367b Mon Sep 17 00:00:00 2001 From: Yoni Yang <76271912+yoni13@users.noreply.github.com> Date: Tue, 18 Mar 2025 00:04:08 +0800 Subject: [PATCH 02/18] feat(ml): ML on Rockchip NPUs (#15241) --- .github/workflows/docker.yml | 7 +- docker/docker-compose.dev.yml | 4 +- docker/docker-compose.prod.yml | 20 +- docker/docker-compose.yml | 18 +- docker/hwaccel.ml.yml | 7 + .../docs/features/ml-hardware-acceleration.md | 22 + docs/docs/install/environment-variables.md | 2 + machine-learning/.gitignore | 19 + machine-learning/Dockerfile | 8 +- machine-learning/app/config.py | 2 + machine-learning/app/conftest.py | 6 + machine-learning/app/main.py | 4 +- machine-learning/app/models/base.py | 27 +- machine-learning/app/models/constants.py | 15 + .../models/facial_recognition/recognition.py | 2 +- machine-learning/app/schemas.py | 1 + .../app/sessions/rknn/__init__.py | 76 + .../app/sessions/rknn/rknnpool.py | 91 + machine-learning/app/test_main.py | 60 +- machine-learning/export/.python-version | 1 + machine-learning/export/Dockerfile | 20 - .../export/{models/__init__.py => README.md} | 0 machine-learning/export/conda-lock.yml | 4328 ----------------- machine-learning/export/env.dev.yaml | 15 - machine-learning/export/env.yaml | 25 - .../export/immich_model_exporter/__init__.py | 0 .../export/immich_model_exporter/export.py | 98 + .../exporters/constants.py | 42 + .../exporters/onnx/__init__.py | 20 + .../exporters/onnx/models/__init__.py | 0 .../exporters/onnx}/models/mclip.py | 36 +- .../exporters/onnx/models/openclip.py | 153 + .../exporters/onnx}/models/util.py | 0 .../immich_model_exporter/exporters/rknn.py | 96 + .../export/immich_model_exporter/run.py | 88 + machine-learning/export/models/openclip.py | 114 - machine-learning/export/models/optimize.py | 49 - machine-learning/export/pyproject.toml | 67 + machine-learning/export/run.py | 113 - machine-learning/export/uv.lock | 1395 ++++++ machine-learning/pyproject.toml | 1 + machine-learning/uv.lock | 79 +- server/src/constants.ts | 12 + 43 files changed, 2417 insertions(+), 4726 deletions(-) create mode 100644 machine-learning/app/sessions/rknn/__init__.py create mode 100644 machine-learning/app/sessions/rknn/rknnpool.py create mode 100644 machine-learning/export/.python-version delete mode 100644 machine-learning/export/Dockerfile rename machine-learning/export/{models/__init__.py => README.md} (100%) delete mode 100644 machine-learning/export/conda-lock.yml delete mode 100644 machine-learning/export/env.dev.yaml delete mode 100644 machine-learning/export/env.yaml create mode 100644 machine-learning/export/immich_model_exporter/__init__.py create mode 100644 machine-learning/export/immich_model_exporter/export.py create mode 100644 machine-learning/export/immich_model_exporter/exporters/constants.py create mode 100644 machine-learning/export/immich_model_exporter/exporters/onnx/__init__.py create mode 100644 machine-learning/export/immich_model_exporter/exporters/onnx/models/__init__.py rename machine-learning/export/{ => immich_model_exporter/exporters/onnx}/models/mclip.py (69%) create mode 100644 machine-learning/export/immich_model_exporter/exporters/onnx/models/openclip.py rename machine-learning/export/{ => immich_model_exporter/exporters/onnx}/models/util.py (100%) create mode 100644 machine-learning/export/immich_model_exporter/exporters/rknn.py create mode 100644 machine-learning/export/immich_model_exporter/run.py delete mode 100644 machine-learning/export/models/openclip.py delete mode 100644 machine-learning/export/models/optimize.py create mode 100644 machine-learning/export/pyproject.toml delete mode 100644 machine-learning/export/run.py create mode 100644 machine-learning/export/uv.lock diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 12f3410310..5d19e5b90f 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -49,7 +49,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - suffix: ["", "-cuda", "-openvino", "-armnn"] + suffix: ["", "-cuda", "-openvino", "-armnn","-rknn"] steps: - name: Login to GitHub Container Registry uses: docker/login-action@v3 @@ -129,6 +129,9 @@ jobs: runner: ubuntu-24.04-arm device: armnn suffix: -armnn + - platforms: linux/arm64 + device: rknn + suffix: -rknn steps: - name: Prepare @@ -454,4 +457,4 @@ jobs: run: exit 1 - name: All jobs passed or skipped if: ${{ !(contains(needs.*.result, 'failure')) }} - run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}" + run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}" \ No newline at end of file diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index f2f814fbd0..78254c7662 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -95,12 +95,12 @@ services: image: immich-machine-learning-dev:latest # extends: # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference + # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference build: context: ../machine-learning dockerfile: Dockerfile args: - - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference + - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference ports: - 3003:3003 volumes: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 559dd55e72..adb00dfbed 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -38,12 +38,12 @@ services: image: immich-machine-learning:latest # extends: # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference + # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference build: context: ../machine-learning dockerfile: Dockerfile args: - - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference + - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference ports: - 3003:3003 volumes: @@ -77,22 +77,12 @@ services: - 5432:5432 healthcheck: 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 start_interval: 30s start_period: 5m 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 # set IMMICH_TELEMETRY_INCLUDE=all in .env to enable metrics @@ -109,7 +99,7 @@ services: # add data source for http://immich-prometheus:9090 to get started immich-grafana: container_name: immich_grafana - command: ['./run.sh', '-disable-reporting'] + command: [ './run.sh', '-disable-reporting' ] ports: - 3000:3000 image: grafana/grafana:11.5.2-ubuntu@sha256:8b5858c447e06fd7a89006b562ba7bba7c4d5813600c7982374c41852adefaeb diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index fd0edf9cb0..6be3189b41 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -33,12 +33,12 @@ services: immich-machine-learning: container_name: immich_machine_learning - # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag. + # For hardware acceleration, add one of -[armnn, cuda, openvino, rknn] to the image tag. # Example tag: ${IMMICH_VERSION:-release}-cuda 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 # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable + # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable volumes: - model-cache:/cache env_file: @@ -67,22 +67,12 @@ services: - ${DB_DATA_LOCATION}:/var/lib/postgresql/data healthcheck: 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 start_interval: 30s start_period: 5m 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 volumes: diff --git a/docker/hwaccel.ml.yml b/docker/hwaccel.ml.yml index d9455d2bb7..c7035047c7 100644 --- a/docker/hwaccel.ml.yml +++ b/docker/hwaccel.ml.yml @@ -13,6 +13,13 @@ services: volumes: - /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver) - /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required) + + rknn: + security_opt: + - systempaths=unconfined + - apparmor=unconfined + devices: + - /dev/dri:/dev/dri cpu: {} diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md index c71c503ace..31d9803c8f 100644 --- a/docs/docs/features/ml-hardware-acceleration.md +++ b/docs/docs/features/ml-hardware-acceleration.md @@ -12,6 +12,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele - ARM NN (Mali) - CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher) - OpenVINO (Intel GPUs such as Iris Xe and Arc) +- RKNN (Rockchip) ## Limitations @@ -19,6 +20,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele - 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. - 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 @@ -33,6 +35,7 @@ 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 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 + - In particular, the `MACHINE_LEARNING_ANN_FP16_TURBO` can significantly improve performance at the cost of very slightly lower accuracy #### CUDA @@ -47,6 +50,16 @@ You do not need to redo any machine learning jobs after enabling hardware accele - 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. +#### 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 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`. @@ -127,3 +140,12 @@ 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. - 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. +- 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) diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index 8b9f74d455..e11547d240 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -170,6 +170,8 @@ 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_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_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. diff --git a/machine-learning/.gitignore b/machine-learning/.gitignore index a259b9f5dc..26e41d6000 100644 --- a/machine-learning/.gitignore +++ b/machine-learning/.gitignore @@ -1,5 +1,24 @@ *.zip *.onnx +*.rknn +*.npy +*_attr__value +*.weight +*.bias +onnx__* +*in_proj_bias +*.proj +*.latent +*.pos_embed +vocab.txt +export/immich_model_exporter/models/**/README.md +tokenizer.json +tokenizer_config.json +special_tokens_map.json +preprocess_cfg.json +config.json +merges.txt +vocab.json upload/ venv/ __pycache__/ diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 800a5ae9e6..4129ffcb8f 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -15,6 +15,8 @@ RUN mkdir /opt/armnn && \ cd /opt/ann && \ sh build.sh +FROM builder-cpu AS builder-rknn + FROM builder-${DEVICE} AS builder ARG DEVICE @@ -77,6 +79,10 @@ COPY --from=builder-armnn \ /opt/ann/build.sh \ /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 ARG DEVICE @@ -123,4 +129,4 @@ ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE ENTRYPOINT ["tini", "--"] CMD ["./start.sh"] -HEALTHCHECK CMD python3 healthcheck.py +HEALTHCHECK CMD python3 healthcheck.py \ No newline at end of file diff --git a/machine-learning/app/config.py b/machine-learning/app/config.py index e77531cbd6..c9816d98c6 100644 --- a/machine-learning/app/config.py +++ b/machine-learning/app/config.py @@ -64,6 +64,8 @@ class Settings(BaseSettings): ann: bool = True ann_fp16_turbo: bool = False ann_tuning_level: int = 2 + rknn: bool = True + rknn_threads: int = 1 preload: PreloadModelData | None = None max_batch_size: MaxBatchSize | None = None diff --git a/machine-learning/app/conftest.py b/machine-learning/app/conftest.py index 6f5d5ea705..50c084215a 100644 --- a/machine-learning/app/conftest.py +++ b/machine-learning/app/conftest.py @@ -136,6 +136,12 @@ def ann_session() -> Iterator[mock.Mock]: 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") def rmtree() -> Iterator[mock.Mock]: with mock.patch("app.models.base.rmtree", autospec=True) as mocked: diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py index 0f50257a4e..4c380dc65f 100644 --- a/machine-learning/app/main.py +++ b/machine-learning/app/main.py @@ -226,9 +226,9 @@ async def load(model: InferenceModel) -> InferenceModel: except FileNotFoundError as e: if model.model_format == ModelFormat.ONNX: raise e - log.exception(e) 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.load() diff --git a/machine-learning/app/models/base.py b/machine-learning/app/models/base.py index 3bbd1a0289..8d1c31b32d 100644 --- a/machine-learning/app/models/base.py +++ b/machine-learning/app/models/base.py @@ -8,6 +8,7 @@ from typing import Any, ClassVar from huggingface_hub import snapshot_download import ann.ann +import app.sessions.rknn as rknn from app.sessions.ort import OrtSession from ..config import clean_name, log, settings @@ -66,12 +67,17 @@ class InferenceModel(ABC): pass def _download(self) -> None: - ignore_patterns = [] if self.model_format == ModelFormat.ARMNN else ["*.armnn"] + ignored_patterns: dict[ModelFormat, list[str]] = { + ModelFormat.ONNX: ["*.armnn", "*.rknn"], + ModelFormat.ARMNN: ["*.rknn"], + ModelFormat.RKNN: ["*.armnn"], + } + snapshot_download( f"immich-app/{clean_name(self.model_name)}", cache_dir=self.cache_dir, local_dir=self.cache_dir, - ignore_patterns=ignore_patterns, + ignore_patterns=ignored_patterns.get(self.model_format, []), ) def _load(self) -> ModelSession: @@ -108,17 +114,25 @@ class InferenceModel(ABC): session: ModelSession = AnnSession(model_path) case ".onnx": session = OrtSession(model_path) + case ".rknn": + session = rknn.RknnSession(model_path) case _: raise ValueError(f"Unsupported model file type: {model_path.suffix}") 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 def model_dir(self) -> Path: return self.cache_dir / self.model_type.value @property def model_path(self) -> Path: - return self.model_dir / f"model.{self.model_format}" + return self.model_path_for_format(self.model_format) @property def model_task(self) -> ModelTask: @@ -155,4 +169,9 @@ class InferenceModel(ABC): @property def _model_format_default(self) -> ModelFormat: - return ModelFormat.ARMNN if ann.ann.is_available and settings.ann else ModelFormat.ONNX + if rknn.is_available: + return ModelFormat.RKNN + elif ann.ann.is_available and settings.ann: + return ModelFormat.ARMNN + else: + return ModelFormat.ONNX diff --git a/machine-learning/app/models/constants.py b/machine-learning/app/models/constants.py index 338a481594..d04ed4d543 100644 --- a/machine-learning/app/models/constants.py +++ b/machine-learning/app/models/constants.py @@ -44,6 +44,18 @@ _OPENCLIP_MODELS = { "nllb-clip-base-siglip__v1", "nllb-clip-large-siglip__mrl", "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", } @@ -65,6 +77,9 @@ _INSIGHTFACE_MODELS = { SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"] +RKNN_SUPPORTED_SOCS = ["rk3566", "rk3568", "rk3576", "rk3588"] +RKNN_COREMASK_SUPPORTED_SOCS = ["rk3576", "rk3588"] + def get_model_source(model_name: str) -> ModelSource | None: cleaned_name = clean_name(model_name) diff --git a/machine-learning/app/models/facial_recognition/recognition.py b/machine-learning/app/models/facial_recognition/recognition.py index 5e8a6f69ec..89851ec708 100644 --- a/machine-learning/app/models/facial_recognition/recognition.py +++ b/machine-learning/app/models/facial_recognition/recognition.py @@ -31,7 +31,7 @@ class FaceRecognizer(InferenceModel): self._add_batch_axis(self.model_path) session = self._make_session(self.model_path) self.model = ArcFaceONNX( - self.model_path.with_suffix(".onnx").as_posix(), + self.model_path_for_format(ModelFormat.ONNX).as_posix(), session=session, ) return session diff --git a/machine-learning/app/schemas.py b/machine-learning/app/schemas.py index d513faed6b..6f9076807d 100644 --- a/machine-learning/app/schemas.py +++ b/machine-learning/app/schemas.py @@ -35,6 +35,7 @@ class ModelType(StrEnum): class ModelFormat(StrEnum): ARMNN = "armnn" ONNX = "onnx" + RKNN = "rknn" class ModelSource(StrEnum): diff --git a/machine-learning/app/sessions/rknn/__init__.py b/machine-learning/app/sessions/rknn/__init__.py new file mode 100644 index 0000000000..2b72c03dec --- /dev/null +++ b/machine-learning/app/sessions/rknn/__init__.py @@ -0,0 +1,76 @@ +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"] diff --git a/machine-learning/app/sessions/rknn/rknnpool.py b/machine-learning/app/sessions/rknn/rknnpool.py new file mode 100644 index 0000000000..f37707ee71 --- /dev/null +++ b/machine-learning/app/sessions/rknn/rknnpool.py @@ -0,0 +1,91 @@ +# 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() diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index 2d489025d7..61424ea732 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -25,6 +25,7 @@ from app.models.facial_recognition.detection import FaceDetector from app.models.facial_recognition.recognition import FaceRecognizer from app.sessions.ann import AnnSession from app.sessions.ort import OrtSession +from app.sessions.rknn import RknnSession, run_inference from .config import Settings, settings from .models.base import InferenceModel @@ -69,6 +70,14 @@ class TestBase: 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: cache_dir = "/test_cache" encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir=cache_dir) @@ -125,7 +134,7 @@ class TestBase: "immich-app/ViT-B-32__openai", cache_dir=encoder.cache_dir, local_dir=encoder.cache_dir, - ignore_patterns=["*.armnn"], + ignore_patterns=["*.armnn", "*.rknn"], ) def test_download_downloads_armnn_if_preferred_format(self, snapshot_download: mock.Mock) -> None: @@ -136,7 +145,18 @@ class TestBase: "immich-app/ViT-B-32__openai", cache_dir=encoder.cache_dir, local_dir=encoder.cache_dir, - ignore_patterns=[], + ignore_patterns=["*.rknn"], + ) + + 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( @@ -328,6 +348,33 @@ class TestAnnSession: 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: embedding = np.random.rand(512).astype(np.float32) cache_dir = Path("test_cache") @@ -829,9 +876,7 @@ class TestLoad: mock_model.clear_cache.assert_not_called() mock_model.load.assert_not_called() - async def test_falls_back_to_onnx_if_other_format_does_not_exist( - self, exception: mock.Mock, warning: mock.Mock - ) -> None: + async def test_falls_back_to_onnx_if_other_format_does_not_exist(self, warning: mock.Mock) -> None: mock_model = mock.Mock(spec=InferenceModel) mock_model.model_name = "test_model_name" mock_model.model_type = ModelType.VISUAL @@ -846,8 +891,9 @@ class TestLoad: mock_model.clear_cache.assert_not_called() assert mock_model.load.call_count == 2 - exception.assert_called_once_with(error) - warning.assert_called_once_with("ARMNN is available, but model 'test_model_name' does not support it.") + warning.assert_called_once_with( + "ARMNN is available, but model 'test_model_name' does not support it.", exc_info=error + ) mock_model.model_format = ModelFormat.ONNX diff --git a/machine-learning/export/.python-version b/machine-learning/export/.python-version new file mode 100644 index 0000000000..e4fba21835 --- /dev/null +++ b/machine-learning/export/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/machine-learning/export/Dockerfile b/machine-learning/export/Dockerfile deleted file mode 100644 index 195e64ab35..0000000000 --- a/machine-learning/export/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -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"] diff --git a/machine-learning/export/models/__init__.py b/machine-learning/export/README.md similarity index 100% rename from machine-learning/export/models/__init__.py rename to machine-learning/export/README.md diff --git a/machine-learning/export/conda-lock.yml b/machine-learning/export/conda-lock.yml deleted file mode 100644 index 17578746de..0000000000 --- a/machine-learning/export/conda-lock.yml +++ /dev/null @@ -1,4328 +0,0 @@ -# This lock file was generated by conda-lock (https://github.com/conda/conda-lock). DO NOT EDIT! -# -# A "lock file" contains a concrete list of package versions (with checksums) to be installed. Unlike -# e.g. `conda env create`, the resulting environment will not change as new package versions become -# available, unless you explicitly update the lock file. -# -# Install this environment as "YOURENV" with: -# conda-lock install -n YOURENV conda-lock.yml -# To update a single package to the latest version compatible with the version constraints in the source: -# conda-lock lock --lockfile conda-lock.yml --update PACKAGE -# To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f env.yaml --lockfile conda-lock.yml -version: 1 -metadata: - content_hash: - linux-64: ceb5c100f77d1cceb7132794f7574e14e198e3bbd864585e4b9ec7034ba73893 - channels: - - url: conda-forge - used_env_vars: [] - - url: nvidia - used_env_vars: [] - - url: pytorch - used_env_vars: [] - platforms: - - linux-64 - sources: - - env.yaml -package: -- name: _libgcc_mutex - version: '0.1' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - hash: - md5: d7c89558ba9fa0495403155b64376d81 - sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 - category: main - optional: false -- name: _openmp_mutex - version: '4.5' - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - llvm-openmp: '>=9.0.1' - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - hash: - md5: 562b26ba2e19059551a811e72ab7f793 - sha256: 84a66275da3a66e3f3e70e9d8f10496d807d01a9e4ec16cd2274cc5e28c478fc - category: main - optional: false -- name: _sysroot_linux-64_curr_repodata_hack - version: '3' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/_sysroot_linux-64_curr_repodata_hack-3-h69a702a_16.conda - hash: - md5: 1c005af0c6ff22814b7c52ee448d4bea - sha256: 6ac30acdbfd3136ee7a1de28af4355165291627e905715611726e674499b0786 - category: main - optional: false -- name: aiohttp - version: 3.9.5 - manager: conda - platform: linux-64 - dependencies: - aiosignal: '>=1.1.2' - attrs: '>=17.3.0' - frozenlist: '>=1.1.1' - libgcc-ng: '>=12' - multidict: '>=4.5,<7.0' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - yarl: '>=1.0,<2.0' - url: https://conda.anaconda.org/conda-forge/linux-64/aiohttp-3.9.5-py311h459d7ec_0.conda - hash: - md5: 0175d2636cc41dc019b51462c13ce225 - sha256: 2eb99d920ef0dcd608e195bb852a64634ecf13f74680796959f1b9d9a9650a7b - category: main - optional: false -- name: aiosignal - version: 1.3.1 - manager: conda - platform: linux-64 - dependencies: - frozenlist: '>=1.1.0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/aiosignal-1.3.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: d1e1eb7e21a9e2c74279d87dafb68156 - sha256: 575c742e14c86575986dc867463582a970463da50b77264cdf54df74f5563783 - category: main - optional: false -- name: aom - version: 3.9.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aom-3.9.1-hac33072_0.conda - hash: - md5: 346722a0be40f6edc53f12640d301338 - sha256: b08ef033817b5f9f76ce62dfcac7694e7b6b4006420372de22494503decac855 - category: main - optional: false -- name: attrs - version: 23.2.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/attrs-23.2.0-pyh71513ae_0.conda - hash: - md5: 5e4c0743c70186509d1412e03c2d8dfa - sha256: 77c7d03bdb243a048fff398cedc74327b7dc79169ebe3b4c8448b0331ea55fea - category: main - optional: false -- name: aws-c-auth - version: 0.7.22 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-cal: '>=0.7.1,<0.7.2.0a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-http: '>=0.8.2,<0.8.3.0a0' - aws-c-io: '>=0.14.10,<0.14.11.0a0' - aws-c-sdkutils: '>=0.1.16,<0.1.17.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.7.22-hbd3ac97_10.conda - hash: - md5: 7ca4abcc98c7521c02f4e8809bbe40df - sha256: c8bf9f9901a56a56b18ab044d67ecde69ee1289881267924dd81670ac34591fe - category: main - optional: false -- name: aws-c-cal - version: 0.7.1 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - libgcc-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-cal-0.7.1-h87b94db_1.conda - hash: - md5: 2d76d2cfdcfe2d5c3883d33d8be919e7 - sha256: f445f38a4170f0ae02cdf13e1bc23cbb826a4b45f39402f02fe5737b0a8ed3a9 - category: main - optional: false -- name: aws-c-common - version: 0.9.23 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-common-0.9.23-h4ab18f5_0.conda - hash: - md5: 94d61ae2b2b701008a9d52ce6bbead27 - sha256: f3eab0ec3f01ddc3ebdc235d4ae1b3b803d83e40f2cd2389bf8c65ab96e90f02 - category: main - optional: false -- name: aws-c-compression - version: 0.2.18 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.9.23,<0.9.24.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-compression-0.2.18-he027950_7.conda - hash: - md5: 11e5cb0b426772974f6416545baee0ce - sha256: d4c70b8716e19fe56a563ab858ab7440f41c2dd927687357a44e69f23001126d - category: main - optional: false -- name: aws-c-event-stream - version: 0.4.2 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-io: '>=0.14.10,<0.14.11.0a0' - aws-checksums: '>=0.1.18,<0.1.19.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.4.2-h7671281_15.conda - hash: - md5: 3b45b0da170f515de8be68155e14955a - sha256: b9546f0637c66d4086a169f4210bf0d569140f41c13f0c1c6826355f51f82494 - category: main - optional: false -- name: aws-c-http - version: 0.8.2 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-cal: '>=0.7.1,<0.7.2.0a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-compression: '>=0.2.18,<0.2.19.0a0' - aws-c-io: '>=0.14.10,<0.14.11.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.8.2-he17ee6b_6.conda - hash: - md5: 4e3d1bb2ade85619ac2163e695c2cc1b - sha256: c2a9501d5e361051457b0afc3ce77496a73c2cf90ad859010812130d512e9271 - category: main - optional: false -- name: aws-c-io - version: 0.14.10 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-cal: '>=0.7.1,<0.7.2.0a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - libgcc-ng: '>=12' - s2n: '>=1.4.17,<1.4.18.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.14.10-h826b7d6_1.conda - hash: - md5: 6961646dded770513a781de4cd5c1fe1 - sha256: 68cb6f708e5e1cf50d98f3c896c7a72ab68e71ce9a69be4eea5dbde5c04bebdc - category: main - optional: false -- name: aws-c-mqtt - version: 0.10.4 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-http: '>=0.8.2,<0.8.3.0a0' - aws-c-io: '>=0.14.10,<0.14.11.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.10.4-hcd6a914_8.conda - hash: - md5: b81c45867558446640306507498b2c6b - sha256: aa6100ed16b1b6eabccca1ee5e36039862e37a7ee91c852de8d4ca0082dcd54e - category: main - optional: false -- name: aws-c-s3 - version: 0.6.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-auth: '>=0.7.22,<0.7.23.0a0' - aws-c-cal: '>=0.7.1,<0.7.2.0a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-http: '>=0.8.2,<0.8.3.0a0' - aws-c-io: '>=0.14.10,<0.14.11.0a0' - aws-checksums: '>=0.1.18,<0.1.19.0a0' - libgcc-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.6.0-h365ddd8_2.conda - hash: - md5: 22339cf124753bafda336167f80e7860 - sha256: 5f82835411b3db3ae9d5db575386d83a8cc6f5f61b414afa6155879b2071c2f6 - category: main - optional: false -- name: aws-c-sdkutils - version: 0.1.16 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.9.23,<0.9.24.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-c-sdkutils-0.1.16-he027950_3.conda - hash: - md5: adbf0c44ca88a3cded175cd809a106b6 - sha256: 0f957d8cebe9c9b4041c858ca9a20619eb3fa866c71b21478a02d51f219d59cb - category: main - optional: false -- name: aws-checksums - version: 0.1.18 - manager: conda - platform: linux-64 - dependencies: - aws-c-common: '>=0.9.23,<0.9.24.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-checksums-0.1.18-he027950_7.conda - hash: - md5: 95611b325a9728ed68b8f7eef2dd3feb - sha256: 094cff556dbf8fdd60505c8285b0a873de101374f568200275d8fd7fb77ad5e9 - category: main - optional: false -- name: aws-crt-cpp - version: 0.27.3 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-auth: '>=0.7.22,<0.7.23.0a0' - aws-c-cal: '>=0.7.1,<0.7.2.0a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-event-stream: '>=0.4.2,<0.4.3.0a0' - aws-c-http: '>=0.8.2,<0.8.3.0a0' - aws-c-io: '>=0.14.10,<0.14.11.0a0' - aws-c-mqtt: '>=0.10.4,<0.10.5.0a0' - aws-c-s3: '>=0.6.0,<0.6.1.0a0' - aws-c-sdkutils: '>=0.1.16,<0.1.17.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.27.3-hda66527_2.conda - hash: - md5: 734875312c8196feecc91f89856da612 - sha256: 3149277f03a55d7dcffdbe489863cacc36a831dbf38b9725bdc653a8c5de134f - category: main - optional: false -- name: aws-sdk-cpp - version: 1.11.329 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-c-common: '>=0.9.23,<0.9.24.0a0' - aws-c-event-stream: '>=0.4.2,<0.4.3.0a0' - aws-checksums: '>=0.1.18,<0.1.19.0a0' - aws-crt-cpp: '>=0.27.3,<0.27.4.0a0' - libcurl: '>=8.8.0,<9.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.3.1,<2.0a0' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/aws-sdk-cpp-1.11.329-h46c3b66_9.conda - hash: - md5: c840f07ec58dc0b06041e7f36550a539 - sha256: 983f6977cc6b25c8bc785b20859970009242b3812e6b4de592ceb17caf93acb6 - category: main - optional: false -- name: azure-core-cpp - version: 1.13.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libcurl: '>=8.8.0,<9.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/azure-core-cpp-1.13.0-h935415a_0.conda - hash: - md5: debd1677c2fea41eb2233a260f48a298 - sha256: b7e0a22295db2e1955f89c69cefc32810309b3af66df986d9fb75d89f98a80f7 - category: main - optional: false -- name: azure-identity-cpp - version: 1.8.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - azure-core-cpp: '>=1.13.0,<1.13.1.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/azure-identity-cpp-1.8.0-hd126650_2.conda - hash: - md5: 36df3cf05459de5d0a41c77c4329634b - sha256: f85452eca3ae0e156b1d1a321a1a9f4f58d44ff45236c0d8602ab96aaad3c6ba - category: main - optional: false -- name: azure-storage-blobs-cpp - version: 12.12.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - azure-core-cpp: '>=1.13.0,<1.13.1.0a0' - azure-storage-common-cpp: '>=12.7.0,<12.7.1.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-blobs-cpp-12.12.0-hd2e3451_0.conda - hash: - md5: 61f1c193452f0daa582f39634627ea33 - sha256: 69a0f5c2a08a1a40524b343060debb8d92295e2cc5805c3db56dad7a41246a93 - category: main - optional: false -- name: azure-storage-common-cpp - version: 12.7.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - azure-core-cpp: '>=1.13.0,<1.13.1.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libxml2: '>=2.12.7,<3.0a0' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-common-cpp-12.7.0-h10ac4d7_1.conda - hash: - md5: ab6d507ad16dbe2157920451d662e4a1 - sha256: 1030fa54497a73eb78c509d451f25701e2e781dc182e7647f55719f1e1f9bee8 - category: main - optional: false -- name: azure-storage-files-datalake-cpp - version: 12.11.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - azure-core-cpp: '>=1.13.0,<1.13.1.0a0' - azure-storage-blobs-cpp: '>=12.12.0,<12.12.1.0a0' - azure-storage-common-cpp: '>=12.7.0,<12.7.1.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/azure-storage-files-datalake-cpp-12.11.0-h325d260_1.conda - hash: - md5: 11d926d1f4a75a1b03d1c053ca20424b - sha256: 1726fa324bb402e52d63227d6cb3f849957cd6841f8cb8aed58bb0c81203befb - category: main - optional: false -- name: binutils - version: '2.40' - manager: conda - platform: linux-64 - dependencies: - binutils_impl_linux-64: '>=2.40,<2.41.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.40-h4852527_7.conda - hash: - md5: df53aa8418f8c289ae9b9665986034f8 - sha256: 75d7f5cda999fe1efe9f1de1be2d3e4ce32b20cbf97d1ef7b770e2e90c062858 - category: main - optional: false -- name: binutils_impl_linux-64 - version: '2.40' - manager: conda - platform: linux-64 - dependencies: - ld_impl_linux-64: '2.40' - sysroot_linux-64: '' - url: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.40-ha1999f0_7.conda - hash: - md5: 3f840c7ed70a96b5ebde8044b2f36f32 - sha256: 230f3136d17fdcf0e6da3a3ae59118570bc18106d79dd29bf2f341338d2a42c4 - category: main - optional: false -- name: binutils_linux-64 - version: '2.40' - manager: conda - platform: linux-64 - dependencies: - binutils_impl_linux-64: 2.40.* - sysroot_linux-64: '' - url: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.40-hb3c18ed_0.conda - hash: - md5: f152f00b4c709e88cd88af1fb50a70b4 - sha256: 2aadece2933f01b5414285ac9390865b59384c8f3d47f7361664cf511ae33ad0 - category: main - optional: false -- name: blas - version: '2.116' - manager: conda - platform: linux-64 - dependencies: - _openmp_mutex: '>=4.5' - blas-devel: 3.9.0 - libblas: 3.9.0 - libcblas: 3.9.0 - libgcc-ng: '>=12' - libgfortran-ng: '' - libgfortran5: '>=10.4.0' - liblapack: 3.9.0 - liblapacke: 3.9.0 - llvm-openmp: '>=14.0.4' - url: https://conda.anaconda.org/conda-forge/linux-64/blas-2.116-mkl.tar.bz2 - hash: - md5: c196a26abf6b4f132c88828ab7c2231c - sha256: 87056ebdc90b6d1ea6726d04d42b844cc302112e80508edbf7bf1f1a4fd3fed2 - category: main - optional: false -- name: blas-devel - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - liblapacke: 3.9.0 - mkl: '>=2022.1.0,<2023.0a0' - mkl-devel: 2022.1.* - url: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 3f92c1c9e1c0e183462c5071aa02cae1 - sha256: a7da65ca4e0322317cbc4d387c4a5f075cdc7fcd12ad9f7f18da758c7532749a - category: main - optional: false -- name: brotli-python - version: 1.1.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py311hb755f60_1.conda - hash: - md5: cce9e7c3f1c307f2a5fb08a2922d6164 - sha256: 559093679e9fdb6061b7b80ca0f9a31fe6ffc213f1dae65bc5c82e2cd1a94107 - category: main - optional: false -- name: bzip2 - version: 1.0.8 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - hash: - md5: 62ee74e96c5ebb0af99386de58cf9553 - sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d - category: main - optional: false -- name: c-ares - version: 1.32.3 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.32.3-h4bc722e_0.conda - hash: - md5: 7624e34ee6baebfc80d67bac76cc9d9d - sha256: 3c5a844bb60b0d52d89c3f1bd828c9856417fe33a6102fd8bbd5c13c3351704a - category: main - optional: false -- name: c-compiler - version: 1.7.0 - manager: conda - platform: linux-64 - dependencies: - binutils: '' - gcc: '' - gcc_linux-64: 12.* - url: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.7.0-hd590300_1.conda - hash: - md5: e9dffe1056994133616378309f932d77 - sha256: 4213b6cbaed673c07f8b79c089f3487afdd56de944f21c4861ead862b7657eb4 - category: main - optional: false -- name: ca-certificates - version: 2024.7.4 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.7.4-hbcca054_0.conda - hash: - md5: 23ab7665c5f63cfb9f1f6195256daac6 - sha256: c1548a3235376f464f9931850b64b02492f379b2f2bb98bc786055329b080446 - category: main - optional: false -- name: cairo - version: 1.18.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - fontconfig: '>=2.14.2,<3.0a0' - fonts-conda-ecosystem: '' - freetype: '>=2.12.1,<3.0a0' - icu: '>=75.1,<76.0a0' - libgcc-ng: '>=12' - libglib: '>=2.80.3,<3.0a0' - libpng: '>=1.6.43,<1.7.0a0' - libstdcxx-ng: '>=12' - libxcb: '>=1.16,<1.17.0a0' - libzlib: '>=1.3.1,<2.0a0' - pixman: '>=0.43.2,<1.0a0' - xorg-libice: '>=1.1.1,<2.0a0' - xorg-libsm: '>=1.2.4,<2.0a0' - xorg-libx11: '>=1.8.9,<2.0a0' - xorg-libxext: '>=1.3.4,<2.0a0' - xorg-libxrender: '>=0.9.11,<0.10.0a0' - zlib: '' - url: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.0-hebfffa5_3.conda - hash: - md5: fceaedf1cdbcb02df9699a0d9b005292 - sha256: aee5b9e6ef71cdfb2aee9beae3ea91910ca761c01c0ef32052e3f94a252fa173 - category: main - optional: false -- name: certifi - version: 2024.7.4 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.7.4-pyhd8ed1ab_0.conda - hash: - md5: 24e7fd6ca65997938fff9e5ab6f653e4 - sha256: dd3577bb5275062c388c46b075dcb795f47f8dac561da7dd35fe504b936934e5 - category: main - optional: false -- name: cffi - version: 1.16.0 - manager: conda - platform: linux-64 - dependencies: - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - pycparser: '' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.16.0-py311hb3a22ac_0.conda - hash: - md5: b3469563ac5e808b0cd92810d0697043 - sha256: b71c94528ca0c35133da4b7ef69b51a0b55eeee570376057f3d2ad60c3ab1444 - category: main - optional: false -- name: charset-normalizer - version: 3.3.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.3.2-pyhd8ed1ab_0.conda - hash: - md5: 7f4a9e3fcff3f6356ae99244a014da6a - sha256: 20cae47d31fdd58d99c4d2e65fbdcefa0b0de0c84e455ba9d6356a4bdbc4b5b9 - category: main - optional: false -- name: colorama - version: 0.4.6 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 3faab06a954c2a04039983f2c4a50d99 - sha256: 2c1b2e9755ce3102bca8d69e8f26e4f087ece73f50418186aee7c74bef8e1698 - category: main - optional: false -- name: coloredlogs - version: 15.0.1 - manager: conda - platform: linux-64 - dependencies: - humanfriendly: '>=9.1' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/coloredlogs-15.0.1-pyhd8ed1ab_3.tar.bz2 - hash: - md5: 7b4fc18b7f66382257c45424eaf81935 - sha256: 0bb37abbf3367add8a8e3522405efdbd06605acfc674488ef52486968f2c119d - category: main - optional: false -- name: cuda-cudart - version: 12.4.127 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-cudart-12.4.127-0.tar.bz2 - hash: - md5: 3f783f2954e59ff9f8df2b2dbc854266 - sha256: 5b229895b7684dfe8f923742036e15ebf9a6a0d304aa32e3792c12931a94c82b - category: main - optional: false -- name: cuda-cupti - version: 12.4.127 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-cupti-12.4.127-0.tar.bz2 - hash: - md5: a77ad7a9e4edf53329898e5fdaccdc34 - sha256: d07f6fbd627a1903d1b1fb2dd0b20fa1121e382408af0d8200808b8d085cb6d6 - category: main - optional: false -- name: cuda-libraries - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - cuda-cudart: '>=12.4.99' - cuda-nvrtc: '>=12.4.99' - cuda-opencl: '>=12.4.99' - libcublas: '>=12.4.2.65' - libcufft: '>=11.2.0.44' - libcufile: '>=1.9.0.20' - libcurand: '>=10.3.5.119' - libcusolver: '>=11.6.0.99' - libcusparse: '>=12.3.0.142' - libnpp: '>=12.2.5.2' - libnvfatbin: '>=12.4.99' - libnvjitlink: '>=12.4.99' - libnvjpeg: '>=12.3.1.89' - url: https://conda.anaconda.org/nvidia/linux-64/cuda-libraries-12.4.0-0.tar.bz2 - hash: - md5: e1f3474ec98d3a4e17d791389c07e769 - sha256: 6d90b85a03c23befd723bf59e21815c09728706a2dde405c9d621856e94d3d0d - category: main - optional: false -- name: cuda-nvrtc - version: 12.4.127 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-nvrtc-12.4.127-0.tar.bz2 - hash: - md5: d4d0da7490723dabe3d5f985ef4a963a - sha256: 14e20e2692104d95987f991faebffba62d4292b8f3cdd17fe1a2165b9a2146c9 - category: main - optional: false -- name: cuda-nvtx - version: 12.4.127 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-nvtx-12.4.127-0.tar.bz2 - hash: - md5: 7c84fc94b4d717932d71f6446e9cbca4 - sha256: f5536c0e2ad3ccf4479a886933512240220916549c03d9dd2a1db73b1e32da94 - category: main - optional: false -- name: cuda-opencl - version: 12.4.127 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/cuda-opencl-12.4.127-0.tar.bz2 - hash: - md5: 3de25496d2b46d83abb8c910ea9842cb - sha256: 94f15dcc7bb763d7afab2042579023188aeeee2e7d563bf2c67d5795526a2376 - category: main - optional: false -- name: cuda-runtime - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - __linux: '' - cuda-libraries: 12.4.0.* - url: https://conda.anaconda.org/conda-forge/noarch/cuda-runtime-12.4.0-ha804496_0.conda - hash: - md5: b760ac3b8e6faaf4f59cb2c47334b4f3 - sha256: 25d9d6e5be65dbb07d298d98489466ac2d11fa0c4abc307995f1e6667bad1b2d - category: main - optional: false -- name: cuda-version - version: '11.8' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/cuda-version-11.8-h70ddcb2_3.conda - hash: - md5: 670f0e1593b8c1d84f57ad5fe5256799 - sha256: 53e0ffc14ea2f2b8c12320fd2aa38b01112763eba851336ff5953b436ae61259 - category: main - optional: false -- name: cudatoolkit - version: 11.8.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/cudatoolkit-11.8.0-h4ba93d1_13.conda - hash: - md5: eb43f5f1f16e2fad2eba22219c3e499b - sha256: 1797bacaf5350f272413c7f50787c01aef0e8eb955df0f0db144b10be2819752 - category: main - optional: false -- name: cudnn - version: 8.9.7.29 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - cuda-version: '>=11.0,<12.0a0' - cudatoolkit: 11.* - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/cudnn-8.9.7.29-hbc23b4c_3.conda - hash: - md5: 4a2d5fab2871d95544de4e1752948d0f - sha256: c553234d447d9938556f067aba7a4686c8e5427e03e740e67199da3782cc420c - category: main - optional: false -- name: cxx-compiler - version: 1.7.0 - manager: conda - platform: linux-64 - dependencies: - c-compiler: 1.7.0 - gxx: '' - gxx_linux-64: 12.* - url: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.7.0-h00ab1b0_1.conda - hash: - md5: 28de2e073db9ca9b72858bee9fb6f571 - sha256: cf895938292cfd4cfa2a06c6d57aa25c33cc974d4ffe52e704ffb67f5577b93f - category: main - optional: false -- name: datasets - version: 2.20.0 - manager: conda - platform: linux-64 - dependencies: - aiohttp: '!=4.0.0a0,!=4.0.0a1' - dill: '>=0.3.0,<0.3.9' - filelock: '' - fsspec: '>=2023.1.0,<=2024.5.0' - huggingface_hub: '>=0.21.2' - multiprocess: '' - numpy: '>=1.17' - packaging: '' - pandas: '' - pyarrow: '>=15.0.0' - pyarrow-hotfix: '' - python: '>=3.8.0' - python-xxhash: '' - pyyaml: '>=5.1' - requests: '>=2.32.2' - tqdm: '>=4.66.3' - url: https://conda.anaconda.org/conda-forge/noarch/datasets-2.20.0-pyhd8ed1ab_0.conda - hash: - md5: 7e2c046cd09a2498bac484413771a9df - sha256: fc8ef02c03076571171a4e2f4abf0a99f2f1614ce2c39e82b1e13b2df1db2c81 - category: main - optional: false -- name: dav1d - version: 1.2.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/dav1d-1.2.1-hd590300_0.conda - hash: - md5: 418c6ca5929a611cbd69204907a83995 - sha256: 22053a5842ca8ee1cf8e1a817138cdb5e647eb2c46979f84153f6ad7bde73020 - category: main - optional: false -- name: dill - version: 0.3.8 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/dill-0.3.8-pyhd8ed1ab_0.conda - hash: - md5: 78745f157d56877a2c6e7b386f66f3e2 - sha256: 482b5b566ca559119b504c53df12b08f3962a5ef8e48061d62fd58a47f8f2ec4 - category: main - optional: false -- name: expat - version: 2.6.2 - manager: conda - platform: linux-64 - dependencies: - libexpat: 2.6.2 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/expat-2.6.2-h59595ed_0.conda - hash: - md5: 53fb86322bdb89496d7579fe3f02fd61 - sha256: 89916c536ae5b85bb8bf0cfa27d751e274ea0911f04e4a928744735c14ef5155 - category: main - optional: false -- name: ffmpeg - version: 7.0.1 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aom: '>=3.9.1,<3.10.0a0' - bzip2: '>=1.0.8,<2.0a0' - dav1d: '>=1.2.1,<1.2.2.0a0' - fontconfig: '>=2.14.2,<3.0a0' - fonts-conda-ecosystem: '' - freetype: '>=2.12.1,<3.0a0' - gmp: '>=6.3.0,<7.0a0' - gnutls: '>=3.7.9,<3.8.0a0' - harfbuzz: '>=9.0.0,<10.0a0' - lame: '>=3.100,<3.101.0a0' - libass: '>=0.17.1,<0.17.2.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libopenvino: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-auto-batch-plugin: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-auto-plugin: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-hetero-plugin: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-intel-cpu-plugin: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-intel-gpu-plugin: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-intel-npu-plugin: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-ir-frontend: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-onnx-frontend: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-paddle-frontend: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-pytorch-frontend: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-tensorflow-frontend: '>=2024.2.0,<2024.2.1.0a0' - libopenvino-tensorflow-lite-frontend: '>=2024.2.0,<2024.2.1.0a0' - libopus: '>=1.3.1,<2.0a0' - libstdcxx-ng: '>=12' - libva: '>=2.22.0,<3.0a0' - libvpx: '>=1.14.1,<1.15.0a0' - libxcb: '>=1.16,<1.17.0a0' - libxml2: '>=2.12.7,<3.0a0' - libzlib: '>=1.3.1,<2.0a0' - openh264: '>=2.4.1,<2.4.2.0a0' - svt-av1: '>=2.1.2,<2.1.3.0a0' - x264: '>=1!164.3095,<1!165' - x265: '>=3.5,<3.6.0a0' - xorg-libx11: '>=1.8.9,<2.0a0' - xz: '>=5.2.6,<6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/ffmpeg-7.0.1-gpl_h9be9148_104.conda - hash: - md5: 107fd9222d9f628608b07b69abba9420 - sha256: b264eb69ddcc15bdbd74e7ce57b96350483abdfaa73d485dd4efcca0f4d8507f - category: main - optional: false -- name: filelock - version: 3.15.4 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.15.4-pyhd8ed1ab_0.conda - hash: - md5: 0e7e4388e9d5283e22b35a9443bdbcc9 - sha256: f78d9c0be189a77cb0c67d02f33005f71b89037a85531996583fb79ff3fe1a0a - category: main - optional: false -- name: font-ttf-dejavu-sans-mono - version: '2.37' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - hash: - md5: 0c96522c6bdaed4b1566d11387caaf45 - sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b - category: main - optional: false -- name: font-ttf-inconsolata - version: '3.000' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 - hash: - md5: 34893075a5c9e55cdafac56607368fc6 - sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c - category: main - optional: false -- name: font-ttf-source-code-pro - version: '2.038' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 - hash: - md5: 4d59c254e01d9cde7957100457e2d5fb - sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 - category: main - optional: false -- name: font-ttf-ubuntu - version: '0.83' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_2.conda - hash: - md5: cbbe59391138ea5ad3658c76912e147f - sha256: c940f6e969143e13a3a9660abb3c7e7e23b8319efb29dbdd5dee0b9939236e13 - category: main - optional: false -- name: fontconfig - version: 2.14.2 - manager: conda - platform: linux-64 - dependencies: - expat: '>=2.5.0,<3.0a0' - freetype: '>=2.12.1,<3.0a0' - libgcc-ng: '>=12' - libuuid: '>=2.32.1,<3.0a0' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda - hash: - md5: 0f69b688f52ff6da70bccb7ff7001d1d - sha256: 155d534c9037347ea7439a2c6da7c24ffec8e5dd278889b4c57274a1d91e0a83 - category: main - optional: false -- name: fonts-conda-ecosystem - version: '1' - manager: conda - platform: linux-64 - dependencies: - fonts-conda-forge: '' - url: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - hash: - md5: fee5683a3f04bd15cbd8318b096a27ab - sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 - category: main - optional: false -- name: fonts-conda-forge - version: '1' - manager: conda - platform: linux-64 - dependencies: - font-ttf-dejavu-sans-mono: '' - font-ttf-inconsolata: '' - font-ttf-source-code-pro: '' - font-ttf-ubuntu: '' - url: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - hash: - md5: f766549260d6815b0c52253f1fb1bb29 - sha256: 53f23a3319466053818540bcdf2091f253cbdbab1e0e9ae7b9e509dcaa2a5e38 - category: main - optional: false -- name: freetype - version: 2.12.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.39,<1.7.0a0' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda - hash: - md5: 9ae35c3d96db2c94ce0cef86efdfa2cb - sha256: b2e3c449ec9d907dd4656cb0dc93e140f447175b125a3824b31368b06c666bb6 - category: main - optional: false -- name: fribidi - version: 1.0.10 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2 - hash: - md5: ac7bc6a654f8f41b352b38f4051135f8 - sha256: 5d7b6c0ee7743ba41399e9e05a58ccc1cfc903942e49ff6f677f6e423ea7a627 - category: main - optional: false -- name: frozenlist - version: 1.4.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/frozenlist-1.4.1-py311h459d7ec_0.conda - hash: - md5: b267e553a337e1878512621e374845c5 - sha256: 56917dda8da109d51a3b25d30256365e1676f7b2fbaf793a3f003e51548bf794 - category: main - optional: false -- name: fsspec - version: 2024.5.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.5.0-pyhff2d567_0.conda - hash: - md5: d73e9932511ef7670b2cc0ebd9dfbd30 - sha256: 34149798edaf7f67251ee09612cd50b52ee8a69b45e63ddb79732085ae7423cd - category: main - optional: false -- name: ftfy - version: 6.1.3 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7,<4.0' - wcwidth: '>=0.2.5' - url: https://conda.anaconda.org/conda-forge/noarch/ftfy-6.1.3-pyhd8ed1ab_0.conda - hash: - md5: b7938352ffb646bbdd85696699ebe2d3 - sha256: 6dd45563e9f32b24edb3dabe91efb996db61890e28899e02a3a7fab603795bdc - category: main - optional: false -- name: gcc - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - gcc_impl_linux-64: 12.4.0.* - url: https://conda.anaconda.org/conda-forge/linux-64/gcc-12.4.0-h236703b_0.conda - hash: - md5: 9485dc28dccde81b12e17f9bdda18f14 - sha256: 4b74a6b5bf035db1715e30ef799ab86c43543dc43ff295b8b09a4f422154d151 - category: main - optional: false -- name: gcc_impl_linux-64 - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - binutils_impl_linux-64: '>=2.40' - libgcc-devel_linux-64: 12.4.0 - libgcc-ng: '>=12.4.0' - libgomp: '>=12.4.0' - libsanitizer: 12.4.0 - libstdcxx-ng: '>=12.4.0' - sysroot_linux-64: '' - url: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-12.4.0-hb2e57f8_0.conda - hash: - md5: 61f3e74c92b7c44191143a661f821bab - sha256: 47dda7dd093c4458a8445e777a7464a53b3f6262127c58a5a6d4ac9fdbe28373 - category: main - optional: false -- name: gcc_linux-64 - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - binutils_linux-64: '2.40' - gcc_impl_linux-64: 12.4.0.* - sysroot_linux-64: '' - url: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-12.4.0-h6b7512a_0.conda - hash: - md5: fec7117a58f5becf76b43dec55064ff9 - sha256: 8806dc5a234f986cd9ead3b2fc6884a4de87a8f6c4af8cf2bcf63e7535ab5019 - category: main - optional: false -- name: gettext - version: 0.22.5 - manager: conda - platform: linux-64 - dependencies: - gettext-tools: 0.22.5 - libasprintf: 0.22.5 - libasprintf-devel: 0.22.5 - libgcc-ng: '>=12' - libgettextpo: 0.22.5 - libgettextpo-devel: 0.22.5 - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/gettext-0.22.5-h59595ed_2.conda - hash: - md5: 219ba82e95d7614cf7140d2a4afc0926 - sha256: 386181254ddd2aed1fccdfc217da5b6545f6df4e9979ad8e08f5e91e22eaf7dc - category: main - optional: false -- name: gettext-tools - version: 0.22.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/gettext-tools-0.22.5-h59595ed_2.conda - hash: - md5: 985f2f453fb72408d6b6f1be0f324033 - sha256: 67d7b1d6fe4f1c516df2000640ec7dcfebf3ff6ea0785f0276870e730c403d33 - category: main - optional: false -- name: gflags - version: 2.2.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - libstdcxx-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-he1b5a44_1004.tar.bz2 - hash: - md5: cddaf2c63ea4a5901cf09524c490ecdc - sha256: a853c0cacf53cfc59e1bca8d6e5cdfe9f38fce836f08c2a69e35429c2a492e77 - category: main - optional: false -- name: glog - version: 0.7.1 - manager: conda - platform: linux-64 - dependencies: - gflags: '>=2.2.2,<2.3.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/glog-0.7.1-hbabe93e_0.conda - hash: - md5: ff862eebdfeb2fd048ae9dc92510baca - sha256: dc824dc1d0aa358e28da2ecbbb9f03d932d976c8dca11214aa1dcdfcbd054ba2 - category: main - optional: false -- name: gmp - version: 6.3.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - hash: - md5: c94a5994ef49749880a8139cf9afcbe1 - sha256: 309cf4f04fec0c31b6771a5809a1909b4b3154a2208f52351e1ada006f4c750c - category: main - optional: false -- name: gmpy2 - version: 2.1.5 - manager: conda - platform: linux-64 - dependencies: - gmp: '>=6.3.0,<7.0a0' - libgcc-ng: '>=12' - mpc: '>=1.3.1,<2.0a0' - mpfr: '>=4.2.1,<5.0a0' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py311hc4f1f91_1.conda - hash: - md5: 30b83b4a5d116d790f8da79a4acac238 - sha256: a174e05ee2531bd81f275bd01557c907faa1d794e68b7c1e73b1d9e7e8f42732 - category: main - optional: false -- name: gnutls - version: 3.7.9 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libidn2: '>=2,<3.0a0' - libstdcxx-ng: '>=12' - libtasn1: '>=4.19.0,<5.0a0' - nettle: '>=3.9.1,<3.10.0a0' - p11-kit: '>=0.24.1,<0.25.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/gnutls-3.7.9-hb077bed_0.conda - hash: - md5: 33eded89024f21659b1975886a4acf70 - sha256: 52d824a5d2b8a5566cd469cae6ad6920469b5a15b3e0ddc609dd29151be71be2 - category: main - optional: false -- name: graphite2 - version: 1.3.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h59595ed_1003.conda - hash: - md5: f87c7b7c2cb45f323ffbce941c78ab7c - sha256: 0595b009f20f8f60f13a6398e7cdcbd2acea5f986633adcf85f5a2283c992add - category: main - optional: false -- name: gxx - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - gcc: 12.4.0.* - gxx_impl_linux-64: 12.4.0.* - url: https://conda.anaconda.org/conda-forge/linux-64/gxx-12.4.0-h236703b_0.conda - hash: - md5: 56cefffbce52071b597fd3eb9208adc9 - sha256: c72b4b41ce3d05ca87299276c0bd5579bf21064a3993e6aebdaca49f021bbea7 - category: main - optional: false -- name: gxx_impl_linux-64 - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - gcc_impl_linux-64: 12.4.0 - libstdcxx-devel_linux-64: 12.4.0 - sysroot_linux-64: '' - url: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-12.4.0-h557a472_0.conda - hash: - md5: 77076175ffd18ef618470991cc38c540 - sha256: b5db532152e6383dd17734ec39e8c1a48aa4fb6b5b6b1dcf28a544edc2b415a7 - category: main - optional: false -- name: gxx_linux-64 - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - binutils_linux-64: '2.40' - gcc_linux-64: 12.4.0 - gxx_impl_linux-64: 12.4.0.* - sysroot_linux-64: '' - url: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-12.4.0-h8489865_0.conda - hash: - md5: 5cf73d936678e6805da39b8ba6be263c - sha256: e2577bc27cb1a287f77f3ad251b4ec1d084bad4792bdfe71b885d395457b4ef4 - category: main - optional: false -- name: h2 - version: 4.1.0 - manager: conda - platform: linux-64 - dependencies: - hpack: '>=4.0,<5' - hyperframe: '>=6.0,<7' - python: '>=3.6.1' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_0.tar.bz2 - hash: - md5: b748fbf7060927a6e82df7cb5ee8f097 - sha256: bfc6a23849953647f4e255c782e74a0e18fe16f7e25c7bb0bc57b83bb6762c7a - category: main - optional: false -- name: harfbuzz - version: 9.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - cairo: '>=1.18.0,<2.0a0' - freetype: '>=2.12.1,<3.0a0' - graphite2: '' - icu: '>=75.1,<76.0a0' - libgcc-ng: '>=12' - libglib: '>=2.80.3,<3.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-9.0.0-hda332d3_1.conda - hash: - md5: 76b32dcf243444aea9c6b804bcfa40b8 - sha256: 973afa37840b4e55e2540018902255cfb0d953aaed6353bb83a4d120f5256767 - category: main - optional: false -- name: hpack - version: 4.0.0 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyh9f0ad1d_0.tar.bz2 - hash: - md5: 914d6646c4dbb1fd3ff539830a12fd71 - sha256: 5dec948932c4f740674b1afb551223ada0c55103f4c7bf86a110454da3d27cb8 - category: main - optional: false -- name: huggingface_hub - version: 0.24.2 - manager: conda - platform: linux-64 - dependencies: - filelock: '' - fsspec: '>=2023.5.0' - packaging: '>=20.9' - python: '>=3.8' - pyyaml: '>=5.1' - requests: '' - tqdm: '>=4.42.1' - typing-extensions: '>=3.7.4.3' - url: https://conda.anaconda.org/conda-forge/noarch/huggingface_hub-0.24.2-pyhd8ed1ab_0.conda - hash: - md5: 58297687dea36924388a1033c5bcad9d - sha256: 06f8d70876214db8e486a54f45c0e524fc7eb853ea4c0b9c36fa33a465b46b22 - category: main - optional: false -- name: humanfriendly - version: '10.0' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/humanfriendly-10.0-py311h38be061_5.conda - hash: - md5: 27dc68fb3173128f42c990ee5864821d - sha256: 90897edfd6f59ee15f6e331e0995d6480f8807be01f90005f9450bb1f514ceab - category: main - optional: false -- name: hyperframe - version: 6.0.1 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_0.tar.bz2 - hash: - md5: 9f765cbfab6870c8435b9eefecd7a1f4 - sha256: e374a9d0f53149328134a8d86f5d72bca4c6dcebed3c0ecfa968c02996289330 - category: main - optional: false -- name: icu - version: '75.1' - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - hash: - md5: 8b189310083baabfb622af68fd9d3ae3 - sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e - category: main - optional: false -- name: idna - version: '3.7' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.7-pyhd8ed1ab_0.conda - hash: - md5: c0cc1420498b17414d8617d0b9f506ca - sha256: 9687ee909ed46169395d4f99a0ee94b80a52f87bed69cd454bb6d37ffeb0ec7b - category: main - optional: false -- name: jinja2 - version: 3.1.4 - manager: conda - platform: linux-64 - dependencies: - markupsafe: '>=2.0' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.4-pyhd8ed1ab_0.conda - hash: - md5: 7b86ecb7d3557821c649b3c31e3eb9f2 - sha256: 27380d870d42d00350d2d52598cddaf02f9505fb24be09488da0c9b8d1428f2d - category: main - optional: false -- name: kernel-headers_linux-64 - version: 3.10.0 - manager: conda - platform: linux-64 - dependencies: - _sysroot_linux-64_curr_repodata_hack: 3.* - url: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-h4a8ded7_16.conda - hash: - md5: ff7f38675b226cfb855aebfc32a13e31 - sha256: a55044e0f61058a5f6bab5e1dd7f15a1fa7a08ec41501dbfca5ab0fc50b9c0c1 - category: main - optional: false -- name: keyutils - version: 1.6.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - hash: - md5: 30186d27e2c9fa62b45fb1476b7200e3 - sha256: 150c05a6e538610ca7c43beb3a40d65c90537497a4f6a5f4d15ec0451b6f5ebb - category: main - optional: false -- name: krb5 - version: 1.21.3 - manager: conda - platform: linux-64 - dependencies: - keyutils: '>=1.6.1,<2.0a0' - libedit: '>=3.1.20191231,<4.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - hash: - md5: 3f43953b7d3fb3aaa1d0d0723d91e368 - sha256: 99df692f7a8a5c27cd14b5fb1374ee55e756631b9c3d659ed3ee60830249b238 - category: main - optional: false -- name: lame - version: '3.100' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2 - hash: - md5: a8832b479f93521a9e7b5b743803be51 - sha256: aad2a703b9d7b038c0f745b853c6bb5f122988fe1a7a096e0e606d9cbec4eaab - category: main - optional: false -- name: lcms2 - version: '2.16' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libjpeg-turbo: '>=3.0.0,<4.0a0' - libtiff: '>=4.6.0,<4.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.16-hb7c19ff_0.conda - hash: - md5: 51bb7010fc86f70eee639b4bb7a894f5 - sha256: 5c878d104b461b7ef922abe6320711c0d01772f4cd55de18b674f88547870041 - category: main - optional: false -- name: ld_impl_linux-64 - version: '2.40' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_7.conda - hash: - md5: b80f2f396ca2c28b8c14c437a4ed1e74 - sha256: 764b6950aceaaad0c67ef925417594dd14cd2e22fff864aeef455ac259263d15 - category: main - optional: false -- name: lerc - version: 4.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2 - hash: - md5: 76bbff344f0134279f225174e9064c8f - sha256: cb55f36dcd898203927133280ae1dc643368af041a48bcf7c026acb7c47b0c12 - category: main - optional: false -- name: libabseil - version: '20240116.2' - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240116.2-cxx17_he02047a_1.conda - hash: - md5: c48fc56ec03229f294176923c3265c05 - sha256: 945396726cadae174a661ce006e3f74d71dbd719219faf7cc74696b267f7b0b5 - category: main - optional: false -- name: libarrow - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - aws-crt-cpp: '>=0.27.3,<0.27.4.0a0' - aws-sdk-cpp: '>=1.11.329,<1.11.330.0a0' - azure-core-cpp: '>=1.13.0,<1.13.1.0a0' - azure-identity-cpp: '>=1.8.0,<1.8.1.0a0' - azure-storage-blobs-cpp: '>=12.12.0,<12.12.1.0a0' - azure-storage-files-datalake-cpp: '>=12.11.0,<12.11.1.0a0' - bzip2: '>=1.0.8,<2.0a0' - gflags: '>=2.2.2,<2.3.0a0' - glog: '>=0.7.1,<0.8.0a0' - libabseil: '>=20240116.2,<20240117.0a0' - libbrotlidec: '>=1.1.0,<1.2.0a0' - libbrotlienc: '>=1.1.0,<1.2.0a0' - libgcc-ng: '>=12' - libgoogle-cloud: '>=2.26.0,<2.27.0a0' - libgoogle-cloud-storage: '>=2.26.0,<2.27.0a0' - libre2-11: '>=2023.9.1,<2024.0a0' - libstdcxx-ng: '>=12' - libutf8proc: '>=2.8.0,<3.0a0' - libzlib: '>=1.3.1,<2.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - orc: '>=2.0.1,<2.0.2.0a0' - re2: '' - snappy: '>=1.2.1,<1.3.0a0' - zstd: '>=1.5.6,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libarrow-17.0.0-h4b47046_2_cpu.conda - hash: - md5: 715b8afa678fd8ef0c2a1a2f5d575d9b - sha256: b40f6d34408b191ca68699d45ac3bbfae7775f0f535166092b69734b30dc0043 - category: main - optional: false -- name: libarrow-acero - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libarrow: 17.0.0 - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libarrow-acero-17.0.0-he02047a_2_cpu.conda - hash: - md5: 43cee94a84bbe6e2d7c123af27140578 - sha256: c710601c8fad60f422c4597e73f176753b69c4d4ef1bd3f0de5615a2ab6a28a2 - category: main - optional: false -- name: libarrow-dataset - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libarrow: 17.0.0 - libarrow-acero: 17.0.0 - libgcc-ng: '>=12' - libparquet: 17.0.0 - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libarrow-dataset-17.0.0-he02047a_2_cpu.conda - hash: - md5: 241bbbd938197304409716fa9510b5f2 - sha256: 6fe4d93d43f53d173c2d2b77bba7aaffd134b1de9ecd3382dc8f0d89d3eb4f2b - category: main - optional: false -- name: libarrow-substrait - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libabseil: '>=20240116.2,<20240117.0a0' - libarrow: 17.0.0 - libarrow-acero: 17.0.0 - libarrow-dataset: 17.0.0 - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libarrow-substrait-17.0.0-hc9a23c6_2_cpu.conda - hash: - md5: 7c6bbc213f37b593c6a90a36b371e48d - sha256: 29c0670577e2a6a298b9119a28dfb6e1b11649587c5abd9e31d89d6b52da8f24 - category: main - optional: false -- name: libasprintf - version: 0.22.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libasprintf-0.22.5-h661eb56_2.conda - hash: - md5: dd197c968bf9760bba0031888d431ede - sha256: 31d58af7eb54e2938123200239277f14893c5fa4b5d0280c8cf55ae10000638b - category: main - optional: false -- name: libasprintf-devel - version: 0.22.5 - manager: conda - platform: linux-64 - dependencies: - libasprintf: 0.22.5 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libasprintf-devel-0.22.5-h661eb56_2.conda - hash: - md5: 02e41ab5834dcdcc8590cf29d9526f50 - sha256: 99d26d272a8203d30b3efbe734a99c823499884d7759b4291674438137c4b5ca - category: main - optional: false -- name: libass - version: 0.17.1 - manager: conda - platform: linux-64 - dependencies: - fontconfig: '>=2.14.2,<3.0a0' - fonts-conda-ecosystem: '' - freetype: '>=2.12.1,<3.0a0' - fribidi: '>=1.0.10,<2.0a0' - harfbuzz: '>=9.0.0,<10.0a0' - libexpat: '>=2.6.2,<3.0a0' - libgcc-ng: '>=12' - libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libass-0.17.1-h39113c1_2.conda - hash: - md5: 25db2ea6b8fefce451369e2cc826f6f4 - sha256: 59ac3fc42b4cee09b04379aa3e91d6d45fdfc8a52afbfa1f9f32e99abbca3137 - category: main - optional: false -- name: libblas - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - mkl: '>=2022.1.0,<2023.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 85f61af03fd291dae33150ffe89dc09a - sha256: 24e656f13b402b6fceb88df386768445ab9beb657d451a8e5a88d4b3380cf7a4 - category: main - optional: false -- name: libbrotlicommon - version: 1.1.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.1.0-hd590300_1.conda - hash: - md5: aec6c91c7371c26392a06708a73c70e5 - sha256: 40f29d1fab92c847b083739af86ad2f36d8154008cf99b64194e4705a1725d78 - category: main - optional: false -- name: libbrotlidec - version: 1.1.0 - manager: conda - platform: linux-64 - dependencies: - libbrotlicommon: 1.1.0 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.1.0-hd590300_1.conda - hash: - md5: f07002e225d7a60a694d42a7bf5ff53f - sha256: 86fc861246fbe5ad85c1b6b3882aaffc89590a48b42d794d3d5c8e6d99e5f926 - category: main - optional: false -- name: libbrotlienc - version: 1.1.0 - manager: conda - platform: linux-64 - dependencies: - libbrotlicommon: 1.1.0 - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.1.0-hd590300_1.conda - hash: - md5: 5fc11c6020d421960607d821310fcd4d - sha256: f751b8b1c4754a2a8dfdc3b4040fa7818f35bbf6b10e905a47d3a194b746b071 - category: main - optional: false -- name: libcblas - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 361bf757b95488de76c4f123805742d3 - sha256: 892ba10508f22310ccfe748df1fd3b6c7f20e7b6f6b79e69ed337863551c1bd8 - category: main - optional: false -- name: libcrc32c - version: 1.1.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - libstdcxx-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libcrc32c-1.1.2-h9c3ff4c_0.tar.bz2 - hash: - md5: c965a5aa0d5c1c37ffc62dff36e28400 - sha256: fd1d153962764433fe6233f34a72cdeed5dcf8a883a85769e8295ce940b5b0c5 - category: main - optional: false -- name: libcublas - version: 12.4.2.65 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcublas-12.4.2.65-0.tar.bz2 - hash: - md5: 220336d76ae4abb949bec97bb2dab6b2 - sha256: 2da035757c494e51b985ee83ac06d83c6e71c73acd05e766a6c9de9846851e83 - category: main - optional: false -- name: libcufft - version: 11.2.0.44 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcufft-11.2.0.44-0.tar.bz2 - hash: - md5: 73db3c332b64b1f07a11b72c3729521b - sha256: 4a2040bd5d425dfaa53d43423ed28a54eb4ae8a637686e7fdc877681b7b99237 - category: main - optional: false -- name: libcufile - version: 1.9.1.3 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcufile-1.9.1.3-0.tar.bz2 - hash: - md5: 9cfc0beef98713d3be47f934251b5154 - sha256: e820395b70a93832a3a8625c637d89c512e18b2158e43f982a74cfe05e168b60 - category: main - optional: false -- name: libcurand - version: 10.3.5.147 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcurand-10.3.5.147-0.tar.bz2 - hash: - md5: e9406bdc4209f8cd5fdb40c8df41d3d9 - sha256: cb15f89cfb48e735d93b0c96c81b36dd05c9b23f0d0228677016d5042bb6a928 - category: main - optional: false -- name: libcurl - version: 8.9.0 - manager: conda - platform: linux-64 - dependencies: - krb5: '>=1.21.3,<1.22.0a0' - libgcc-ng: '>=12' - libnghttp2: '>=1.58.0,<2.0a0' - libssh2: '>=1.11.0,<2.0a0' - libzlib: '>=1.3.1,<2.0a0' - openssl: '>=3.3.1,<4.0a0' - zstd: '>=1.5.6,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.9.0-hdb1bdb2_0.conda - hash: - md5: 5badfbdb2688d8aaca7bd3c98d557b97 - sha256: ff97a3160117385649e1b7e8b84fefb3561fceae09bb48d2bfdf37bc2b6bfdc9 - category: main - optional: false -- name: libcusolver - version: 11.6.0.99 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcusolver-11.6.0.99-0.tar.bz2 - hash: - md5: 3aa936851b8594bdcb334cf913401d3a - sha256: e7565e47d31e637ee64c6fd8598430ff0b0cba10845e12fefd553a3470f0b4c3 - category: main - optional: false -- name: libcusparse - version: 12.3.0.142 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libcusparse-12.3.0.142-0.tar.bz2 - hash: - md5: a646db65445a2746b53af088248cb341 - sha256: 61f2537cd0dfcbf348232abca9a34ea34e5ddf3da73bc3e0d66b73c033e1718b - category: main - optional: false -- name: libdeflate - version: '1.20' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.20-hd590300_0.conda - hash: - md5: 8e88f9389f1165d7c0936fe40d9a9a79 - sha256: f8e0f25c382b1d0b87a9b03887a34dbd91485453f1ea991fef726dba57373612 - category: main - optional: false -- name: libdrm - version: 2.4.122 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpciaccess: '>=0.18,<0.19.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.122-h4ab18f5_0.conda - hash: - md5: bbfc4dbe5e97b385ef088f354d65e563 - sha256: 74c59a29b76bafbb022389c7cfa9b33b8becd7879b2c6b25a1a99735bf4e9c81 - category: main - optional: false -- name: libedit - version: 3.1.20191231 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - ncurses: '>=6.2,<7.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2 - hash: - md5: 4d331e44109e3f0e19b4cb8f9b82f3e1 - sha256: a57d37c236d8f7c886e01656f4949d9dcca131d2a0728609c6f7fa338b65f1cf - category: main - optional: false -- name: libev - version: '4.33' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - hash: - md5: 172bf1cd1ff8629f2b1179945ed45055 - sha256: 1cd6048169fa0395af74ed5d8f1716e22c19a81a8a36f934c110ca3ad4dd27b4 - category: main - optional: false -- name: libevent - version: 2.1.12 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.1.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.12-hf998b51_1.conda - hash: - md5: a1cfcc585f0c42bf8d5546bb1dfb668d - sha256: 2e14399d81fb348e9d231a82ca4d816bf855206923759b69ad006ba482764131 - category: main - optional: false -- name: libexpat - version: 2.6.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.2-h59595ed_0.conda - hash: - md5: e7ba12deb7020dd080c6c70e7b6f6a3d - sha256: 331bb7c7c05025343ebd79f86ae612b9e1e74d2687b8f3179faec234f986ce19 - category: main - optional: false -- name: libffi - version: 3.4.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - hash: - md5: d645c6d2ac96843a2bfaccd2d62b3ac3 - sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e - category: main - optional: false -- name: libgcc-devel_linux-64 - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - url: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-12.4.0-ha4f9413_100.conda - hash: - md5: cc5767cb4e052330106536a9fb34f077 - sha256: edafdf2700aa490f2659180667545f9e7e1fef7cfe89123a5c1bd829a9cfd6d2 - category: main - optional: false -- name: libgcc-ng - version: 14.1.0 - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - _openmp_mutex: '>=4.5' - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h77fa898_0.conda - hash: - md5: ca0fad6a41ddaef54a153b78eccb5037 - sha256: b8e869ac96591cda2704bf7e77a301025e405227791a0bddf14a3dac65125538 - category: main - optional: false -- name: libgettextpo - version: 0.22.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libgettextpo-0.22.5-h59595ed_2.conda - hash: - md5: 172bcc51059416e7ce99e7b528cede83 - sha256: e2f784564a2bdc6f753f00f63cc77c97601eb03bc89dccc4413336ec6d95490b - category: main - optional: false -- name: libgettextpo-devel - version: 0.22.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libgettextpo: 0.22.5 - url: https://conda.anaconda.org/conda-forge/linux-64/libgettextpo-devel-0.22.5-h59595ed_2.conda - hash: - md5: b63d9b6da3653179a278077f0de20014 - sha256: 695eb2439ad4a89e4205dd675cc52fba5cef6b5d41b83f07cdbf4770a336cc15 - category: main - optional: false -- name: libgfortran-ng - version: 14.1.0 - manager: conda - platform: linux-64 - dependencies: - libgfortran5: 14.1.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.1.0-h69a702a_0.conda - hash: - md5: f4ca84fbd6d06b0a052fb2d5b96dde41 - sha256: ef624dacacf97b2b0af39110b36e2fd3e39e358a1a6b7b21b85c9ac22d8ffed9 - category: main - optional: false -- name: libgfortran5 - version: 14.1.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=14.1.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.1.0-hc5f4f2c_0.conda - hash: - md5: 6456c2620c990cd8dde2428a27ba0bc5 - sha256: a67d66b1e60a8a9a9e4440cee627c959acb4810cb182e089a4b0729bfdfbdf90 - category: main - optional: false -- name: libglib - version: 2.80.3 - manager: conda - platform: linux-64 - dependencies: - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.3.1,<2.0a0' - pcre2: '>=10.44,<10.45.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.80.3-h8a4344b_1.conda - hash: - md5: 6ea440297aacee4893f02ad759e6ffbc - sha256: 5f5854a7cee117d115009d8f22a70d5f9e28f09cb6e453e8f1dd712e354ecec9 - category: main - optional: false -- name: libgomp - version: 14.1.0 - manager: conda - platform: linux-64 - dependencies: - _libgcc_mutex: '0.1' - url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_0.conda - hash: - md5: ae061a5ed5f05818acdf9adab72c146d - sha256: 7699df61a1f6c644b3576a40f54791561f2845983120477a16116b951c9cdb05 - category: main - optional: false -- name: libgoogle-cloud - version: 2.26.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libabseil: '>=20240116.2,<20240117.0a0' - libcurl: '>=8.8.0,<9.0a0' - libgcc-ng: '>=12' - libgrpc: '>=1.62.2,<1.63.0a0' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-2.26.0-h26d7fe4_0.conda - hash: - md5: 7b9d4c93870fb2d644168071d4d76afb - sha256: c6caa2d4c375c6c5718e6223bb20ccf6305313c0fef2a66499b4f6cdaa299635 - category: main - optional: false -- name: libgoogle-cloud-storage - version: 2.26.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libabseil: '' - libcrc32c: '>=1.1.2,<1.2.0a0' - libcurl: '' - libgcc-ng: '>=12' - libgoogle-cloud: 2.26.0 - libstdcxx-ng: '>=12' - libzlib: '>=1.3.1,<2.0a0' - openssl: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libgoogle-cloud-storage-2.26.0-ha262f82_0.conda - hash: - md5: 89b53708fd67762b26c38c8ecc5d323d - sha256: 7c16bf2e5aa6b5e42450c218fdfa7d5ff1da952c5a5c821c001ab3fd940c2aed - category: main - optional: false -- name: libgrpc - version: 1.62.2 - manager: conda - platform: linux-64 - dependencies: - c-ares: '>=1.28.1,<2.0a0' - libabseil: '>=20240116.1,<20240117.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libre2-11: '>=2023.9.1,<2024.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - openssl: '>=3.2.1,<4.0a0' - re2: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.62.2-h15f2491_0.conda - hash: - md5: 8dabe607748cb3d7002ad73cd06f1325 - sha256: 28241ed89335871db33cb6010e9ccb2d9e9b6bb444ddf6884f02f0857363c06a - category: main - optional: false -- name: libhwloc - version: 2.11.1 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libxml2: '>=2.12.7,<3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.1-default_hecaa2ac_1000.conda - hash: - md5: f54aeebefb5c5ff84eca4fb05ca8aa3a - sha256: 8473a300e10b79557ce0ac81602506b47146aff3df4cc3568147a7dd07f480a2 - category: main - optional: false -- name: libiconv - version: '1.17' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - hash: - md5: d66573916ffcf376178462f1b61c941e - sha256: 8ac2f6a9f186e76539439e50505d98581472fedb347a20e7d1f36429849f05c9 - category: main - optional: false -- name: libidn2 - version: 2.3.7 - manager: conda - platform: linux-64 - dependencies: - gettext: '>=0.21.1,<1.0a0' - libgcc-ng: '>=12' - libunistring: '>=0,<1.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libidn2-2.3.7-hd590300_0.conda - hash: - md5: 2b7b0d827c6447cc1d85dc06d5b5de46 - sha256: 253f9be445c58bf07b39d8f67ac08bccc5010c75a8c2070cddfb6c20e1ca4f4f - category: main - optional: false -- name: libjpeg-turbo - version: 3.0.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.0.0-hd590300_1.conda - hash: - md5: ea25936bb4080d843790b586850f82b8 - sha256: b954e09b7e49c2f2433d6f3bb73868eda5e378278b0f8c1dd10a7ef090e14f2f - category: main - optional: false -- name: liblapack - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: a2f166748917d6d6e4707841ca1f519e - sha256: d6201f860b2d76ed59027e69c2bbad6d1cb211a215ec9705cc487cde488fa1fa - category: main - optional: false -- name: liblapacke - version: 3.9.0 - manager: conda - platform: linux-64 - dependencies: - libblas: 3.9.0 - libcblas: 3.9.0 - liblapack: 3.9.0 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-16_linux64_mkl.tar.bz2 - hash: - md5: 44ccc4d4dca6a8d57fa17442bc64b5a1 - sha256: 935036dc46c483cba8288c6de58d461ab3f42915715ffe9485105ad1dd203a0e - category: main - optional: false -- name: libnghttp2 - version: 1.58.0 - manager: conda - platform: linux-64 - dependencies: - c-ares: '>=1.23.0,<2.0a0' - libev: '>=4.33,<5.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - openssl: '>=3.2.0,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.58.0-h47da74e_1.conda - hash: - md5: 700ac6ea6d53d5510591c4344d5c989a - sha256: 1910c5306c6aa5bcbd623c3c930c440e9c77a5a019008e1487810e3c1d3716cb - category: main - optional: false -- name: libnpp - version: 12.2.5.2 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libnpp-12.2.5.2-0.tar.bz2 - hash: - md5: 4cb189c81bfee49c130935d140e1e627 - sha256: 817f7d9e3784efcb2d8948b1573a9944f0963b836006d384fda462f93a4a8177 - category: main - optional: false -- name: libnsl - version: 2.0.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - hash: - md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 - sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 - category: main - optional: false -- name: libnvfatbin - version: 12.4.127 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libnvfatbin-12.4.127-0.tar.bz2 - hash: - md5: 87530433a48cf2cf5385ba5d40630b77 - sha256: 9521855837d1463bf616818061822696e2c7eb8fb81b3515c24e4a65031dddb5 - category: main - optional: false -- name: libnvjitlink - version: 12.4.99 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libnvjitlink-12.4.99-0.tar.bz2 - hash: - md5: 9c9a855b7cf5cf743c6ef02a6d727ae1 - sha256: 951424c7f0f69e80e10f18913b7d59e2f6531260c198a25b21dca9b5fbb8c059 - category: main - optional: false -- name: libnvjpeg - version: 12.3.1.89 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/nvidia/linux-64/libnvjpeg-12.3.1.89-0.tar.bz2 - hash: - md5: 3ae37c5278bb34ab2646cdb888499ef5 - sha256: 4e910c331fe2cc3843190b9ab0d5654150fefae214124891f29ddd4e2e58b28a - category: main - optional: false -- name: libopenvino - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - pugixml: '>=1.14,<1.15.0a0' - tbb: '>=2021.12.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-2024.2.0-h2da1b83_1.conda - hash: - md5: 9511859bf5221238a2d3fb5322af01d5 - sha256: 32ce474983e78acb8636e580764e3d28899a7b0a2a61a538677e9bca09e95415 - category: main - optional: false -- name: libopenvino-auto-batch-plugin - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - tbb: '>=2021.12.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-auto-batch-plugin-2024.2.0-hb045406_1.conda - hash: - md5: 70d82a64e6d07f4d6e07cae6b0bd4bd1 - sha256: 083e72464866b857ff272242f887b46a5527e20e41d292db55a4fa10aa0808c6 - category: main - optional: false -- name: libopenvino-auto-plugin - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - tbb: '>=2021.12.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-auto-plugin-2024.2.0-hb045406_1.conda - hash: - md5: f1e2a8ded23cef03804c4edb2edfb986 - sha256: db945b8a8d716d0c6f80cc5f07fd79692c8a941a9ee653aab6f7d2496f6f163b - category: main - optional: false -- name: libopenvino-hetero-plugin - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - pugixml: '>=1.14,<1.15.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-hetero-plugin-2024.2.0-h5c03a75_1.conda - hash: - md5: 95d2d3baaa1e456ef65c713a5d99b815 - sha256: 6924426d9f88a54bfcc8aa2f5d9d7aeb69c839f308cd3b37aedc667157fc90f1 - category: main - optional: false -- name: libopenvino-intel-cpu-plugin - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - pugixml: '>=1.14,<1.15.0a0' - tbb: '>=2021.12.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-cpu-plugin-2024.2.0-h2da1b83_1.conda - hash: - md5: 9e49f87d8f99dc9724f52b3fac904106 - sha256: f2a4f0705e56ad8e25e4b20929e74ab0c7d5867cd52f315510dff37ea6508c38 - category: main - optional: false -- name: libopenvino-intel-gpu-plugin - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - ocl-icd: '>=2.3.2,<3.0a0' - pugixml: '>=1.14,<1.15.0a0' - tbb: '>=2021.12.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-gpu-plugin-2024.2.0-h2da1b83_1.conda - hash: - md5: a9712fae44d01d906e228c49235e3b89 - sha256: c15a90baed7c3ad46c51d2ec70087cc3fb947dbeaea7e4bc93f785e9d12af092 - category: main - optional: false -- name: libopenvino-intel-npu-plugin - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-intel-npu-plugin-2024.2.0-he02047a_1.conda - hash: - md5: 5c2d064181e686cf5cfac6f1a1ee4e91 - sha256: c2f4f1685b3662b0f18f6647fe7a46a0c061f78e017e3d9815e326171f342ba6 - category: main - optional: false -- name: libopenvino-ir-frontend - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - pugixml: '>=1.14,<1.15.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-ir-frontend-2024.2.0-h5c03a75_1.conda - hash: - md5: 89addf0fc0f489fa0c076f1c8c0d62bf - sha256: eb183fa65b43cc944ad3d1528cdb5c533d3b4ccdd8ed44612e2c89f962a020ce - category: main - optional: false -- name: libopenvino-onnx-frontend - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-onnx-frontend-2024.2.0-h07e8aee_1.conda - hash: - md5: 9b0a13989b35302e47da13842683804d - sha256: 3f7ea37f5d8f052a1a162d864c01b4ba477c05734351847e9136a5ebe84ac827 - category: main - optional: false -- name: libopenvino-paddle-frontend - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-paddle-frontend-2024.2.0-h07e8aee_1.conda - hash: - md5: 7b3680d3fd00e1f91d5faf9c97c7ae78 - sha256: da2fcf5e9962d5c5e1d47d52f84635648952354c30205c5908332af5999625bc - category: main - optional: false -- name: libopenvino-pytorch-frontend - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-pytorch-frontend-2024.2.0-he02047a_1.conda - hash: - md5: ac43b516c128411f84f1e19c875998f1 - sha256: 077470fd8a48b4aafbb46a6ceccd9697a82ec16cce5dcb56282711ec04852e1d - category: main - optional: false -- name: libopenvino-tensorflow-frontend - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libabseil: '>=20240116.2,<20240117.0a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - snappy: '>=1.2.0,<1.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-tensorflow-frontend-2024.2.0-h39126c6_1.conda - hash: - md5: 11acf52cac790edcf087b89e83834f7d - sha256: 0558659f340bc22a918750e1142a9215bac66fb8cde62279559f4a22d7d11be1 - category: main - optional: false -- name: libopenvino-tensorflow-lite-frontend - version: 2024.2.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libopenvino: 2024.2.0 - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libopenvino-tensorflow-lite-frontend-2024.2.0-he02047a_1.conda - hash: - md5: e7f91b35e3aa7abe880fc9192a761fc0 - sha256: 896b19b23e0649cdadf972c7380f74b766012feaea1417ab2fc4efb4de049cd4 - category: main - optional: false -- name: libopus - version: 1.3.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 - hash: - md5: 15345e56d527b330e1cacbdf58676e8f - sha256: 0e1c2740ebd1c93226dc5387461bbcf8142c518f2092f3ea7551f77755decc8f - category: main - optional: false -- name: libparquet - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libarrow: 17.0.0 - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libthrift: '>=0.19.0,<0.19.1.0a0' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libparquet-17.0.0-h9e5060d_2_cpu.conda - hash: - md5: e4a82f087e5b915a7ee4cd199a7678df - sha256: d7283a8bf46b6203b600fa87dc94505fc54ac893071a5e8a44024b75e1c2f82f - category: main - optional: false -- name: libpciaccess - version: '0.18' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.18-hd590300_0.conda - hash: - md5: 48f4330bfcd959c3cfb704d424903c82 - sha256: c0a30ac74eba66ea76a4f0a39acc7833f5ed783a632ca3bb6665b2d81aabd2fb - category: main - optional: false -- name: libpng - version: 1.6.43 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.43-h2797004_0.conda - hash: - md5: 009981dd9cfcaa4dbfa25ffaed86bcae - sha256: 502f6ff148ac2777cc55ae4ade01a8fc3543b4ffab25c4e0eaa15f94e90dd997 - category: main - optional: false -- name: libprotobuf - version: 4.25.3 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20240116.1,<20240117.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-4.25.3-h08a7969_0.conda - hash: - md5: 6945825cebd2aeb16af4c69d97c32c13 - sha256: 70e0eef046033af2e8d21251a785563ad738ed5281c74e21c31c457780845dcd - category: main - optional: false -- name: libre2-11 - version: 2023.09.01 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20240116.1,<20240117.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2023.09.01-h5a48ba9_2.conda - hash: - md5: 41c69fba59d495e8cf5ffda48a607e35 - sha256: 3f3c65fe0e9e328b4c1ebc2b622727cef3e5b81b18228cfa6cf0955bc1ed8eff - category: main - optional: false -- name: libsanitizer - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12.4.0' - libstdcxx-ng: '>=12.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-12.4.0-h46f95d5_0.conda - hash: - md5: 23f5c8ad2a46976a9eee4d21392fa421 - sha256: 6ab05aa2156fb4ebc502c5b4a991eff31dbcba5a7aff4f4c43040b610413101a - category: main - optional: false -- name: libsentencepiece - version: 0.2.0 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20240116.2,<20240117.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libsentencepiece-0.2.0-he81a138_2.conda - hash: - md5: 5000f6c9352c853e4c742e2ec88f9a43 - sha256: 977e520078f3a3278b39719eb348568b584dd55ca009732563edd16d3ba476e7 - category: main - optional: false -- name: libsqlite - version: 3.46.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.46.0-hde9e2c9_0.conda - hash: - md5: 18aa975d2094c34aef978060ae7da7d8 - sha256: daee3f68786231dad457d0dfde3f7f1f9a7f2018adabdbb864226775101341a8 - category: main - optional: false -- name: libssh2 - version: 1.11.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - openssl: '>=3.1.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda - hash: - md5: 1f5a58e686b13bcfde88b93f547d23fe - sha256: 50e47fd9c4f7bf841a11647ae7486f65220cfc988ec422a4475fe8d5a823824d - category: main - optional: false -- name: libstdcxx-devel_linux-64 - version: 12.4.0 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - url: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-12.4.0-ha4f9413_100.conda - hash: - md5: 0351f91f429a046542bba7255438fa04 - sha256: f2cbcdd1e603cb21413c697ffa3b30d7af3fd26128a92b3adc6160351b3acd2e - category: main - optional: false -- name: libstdcxx-ng - version: 14.1.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: 14.1.0 - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.1.0-hc0a3c3a_0.conda - hash: - md5: 1cb187a157136398ddbaae90713e2498 - sha256: 88c42b388202ffe16adaa337e36cf5022c63cf09b0405cf06fc6aeacccbe6146 - category: main - optional: false -- name: libtasn1 - version: 4.19.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libtasn1-4.19.0-h166bdaf_0.tar.bz2 - hash: - md5: 93840744a8552e9ebf6bb1a5dffc125a - sha256: 5bfeada0e1c6ec2574afe2d17cdbc39994d693a41431338a6cb9dfa7c4d7bfc8 - category: main - optional: false -- name: libthrift - version: 0.19.0 - manager: conda - platform: linux-64 - dependencies: - libevent: '>=2.1.12,<2.1.13.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - openssl: '>=3.1.3,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libthrift-0.19.0-hb90f79a_1.conda - hash: - md5: 8cdb7d41faa0260875ba92414c487e2d - sha256: 719add2cf20d144ef9962c57cd0f77178259bdb3aae1cded2e2b2b7c646092f5 - category: main - optional: false -- name: libtiff - version: 4.6.0 - manager: conda - platform: linux-64 - dependencies: - lerc: '>=4.0.0,<5.0a0' - libdeflate: '>=1.20,<1.21.0a0' - libgcc-ng: '>=12' - libjpeg-turbo: '>=3.0.0,<4.0a0' - libstdcxx-ng: '>=12' - libwebp-base: '>=1.3.2,<2.0a0' - libzlib: '>=1.2.13,<2.0.0a0' - xz: '>=5.2.6,<6.0a0' - zstd: '>=1.5.5,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.6.0-h1dd3fc0_3.conda - hash: - md5: 66f03896ffbe1a110ffda05c7a856504 - sha256: fc3b210f9584a92793c07396cb93e72265ff3f1fa7ca629128bf0a50d5cb15e4 - category: main - optional: false -- name: libunistring - version: 0.9.10 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/libunistring-0.9.10-h7f98852_0.tar.bz2 - hash: - md5: 7245a044b4a1980ed83196176b78b73a - sha256: e88c45505921db29c08df3439ddb7f771bbff35f95e7d3103bf365d5d6ce2a6d - category: main - optional: false -- name: libutf8proc - version: 2.8.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.8.0-h166bdaf_0.tar.bz2 - hash: - md5: ede4266dc02e875fe1ea77b25dd43747 - sha256: 49082ee8d01339b225f7f8c60f32a2a2c05fe3b16f31b554b4fb2c1dea237d1c - category: main - optional: false -- name: libuuid - version: 2.38.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - hash: - md5: 40b61aab5c7ba9ff276c41cfffe6b80b - sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 - category: main - optional: false -- name: libva - version: 2.22.0 - manager: conda - platform: linux-64 - dependencies: - libdrm: '>=2.4.121,<2.5.0a0' - libgcc-ng: '>=12' - libxcb: '>=1.16,<1.17.0a0' - wayland: '>=1.23.0,<2.0a0' - wayland-protocols: '' - xorg-libx11: '>=1.8.9,<2.0a0' - xorg-libxext: '>=1.3.4,<2.0a0' - xorg-libxfixes: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libva-2.22.0-hb711507_0.conda - hash: - md5: d12f659072132c9d16e497073fc1f68b - sha256: 8a67bda4308a939b2b25337cac1bc7950a1ee755d009c020ab739c4e0607fc2d - category: main - optional: false -- name: libvpx - version: 1.14.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libvpx-1.14.1-hac33072_0.conda - hash: - md5: cde393f461e0c169d9ffb2fc70f81c33 - sha256: e7d2daf409c807be48310fcc8924e481b62988143f582eb3a58c5523a6763b13 - category: main - optional: false -- name: libwebp-base - version: 1.4.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.4.0-hd590300_0.conda - hash: - md5: b26e8aa824079e1be0294e7152ca4559 - sha256: 49bc5f6b1e11cb2babf2a2a731d1a680a5e08a858280876a779dbda06c78c35f - category: main - optional: false -- name: libxcb - version: '1.16' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - pthread-stubs: '' - xorg-libxau: '>=1.0.11,<2.0a0' - xorg-libxdmcp: '' - url: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.16-hd590300_0.conda - hash: - md5: 151cba22b85a989c2d6ef9633ffee1e4 - sha256: 7180375f37fd264bb50672a63da94536d4abd81ccec059e932728ae056324b3a - category: main - optional: false -- name: libxcrypt - version: 4.4.36 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - hash: - md5: 5aa797f8787fe7a17d1b0821485b5adc - sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c - category: main - optional: false -- name: libxml2 - version: 2.12.7 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - icu: '>=75.1,<76.0a0' - libgcc-ng: '>=12' - libiconv: '>=1.17,<2.0a0' - libzlib: '>=1.3.1,<2.0a0' - xz: '>=5.2.6,<6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-he7c6b58_4.conda - hash: - md5: 08a9265c637230c37cb1be4a6cad4536 - sha256: 10e9e0ac52b9a516a17edbc07f8d559e23778e54f1a7721b2e0e8219284fed3b - category: main - optional: false -- name: libzlib - version: 1.3.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda - hash: - md5: 57d7dc60e9325e3de37ff8dffd18e814 - sha256: adf6096f98b537a11ae3729eaa642b0811478f0ea0402ca67b5108fe2cb0010d - category: main - optional: false -- name: llvm-openmp - version: 15.0.7 - manager: conda - platform: linux-64 - dependencies: - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-15.0.7-h0cdce71_0.conda - hash: - md5: 589c9a3575a050b583241c3d688ad9aa - sha256: 7c67d383a8b1f3e7bf9e046e785325c481f6868194edcfb9d78d261da4ad65d4 - category: main - optional: false -- name: lz4-c - version: 1.9.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda - hash: - md5: 318b08df404f9c9be5712aaa5a6f0bb0 - sha256: 1b4c105a887f9b2041219d57036f72c4739ab9e9fe5a1486f094e58c76b31f5f - category: main - optional: false -- name: markdown-it-py - version: 3.0.0 - manager: conda - platform: linux-64 - dependencies: - mdurl: '>=0.1,<1' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - hash: - md5: 93a8e71256479c62074356ef6ebf501b - sha256: c041b0eaf7a6af3344d5dd452815cdc148d6284fec25a4fa3f4263b3a021e962 - category: main - optional: false -- name: markupsafe - version: 2.1.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.5-py311h459d7ec_0.conda - hash: - md5: a322b4185121935c871d201ae00ac143 - sha256: 14912e557a6576e03f65991be89e9d289c6e301921b6ecfb4e7186ba974f453d - category: main - optional: false -- name: mdurl - version: 0.1.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_0.conda - hash: - md5: 776a8dd9e824f77abac30e6ef43a8f7a - sha256: 64073dfb6bb429d52fff30891877b48c7ec0f89625b1bf844905b66a81cce6e1 - category: main - optional: false -- name: mkl - version: 2022.1.0 - manager: conda - platform: linux-64 - dependencies: - _openmp_mutex: '>=4.5' - llvm-openmp: '>=14.0.3' - tbb: 2021.* - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-2022.1.0-h84fe81f_915.tar.bz2 - hash: - md5: b9c8f925797a93dbff45e1626b025a6b - sha256: 767318c4f2057822a7ebc238d6065ce12c6ae60df4ab892758adb79b1057ce02 - category: main - optional: false -- name: mkl-devel - version: 2022.1.0 - manager: conda - platform: linux-64 - dependencies: - mkl: 2022.1.0 - mkl-include: 2022.1.0 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2022.1.0-ha770c72_916.tar.bz2 - hash: - md5: 69ba49e445f87aea2cba343a71a35ca2 - sha256: 93d957608b17ada3039ff0acad2b8596451caa6829b3502fe87375e639ffc34e - category: main - optional: false -- name: mkl-include - version: 2022.1.0 - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2022.1.0-h84fe81f_915.tar.bz2 - hash: - md5: 2dcd1acca05c11410d4494d7fc7dfa2a - sha256: 63415fe64e99f8323d0191d45ea5b1ec3973317e728b9071267ffb7ff3b38364 - category: main - optional: false -- name: mpc - version: 1.3.1 - manager: conda - platform: linux-64 - dependencies: - gmp: '>=6.2.1,<7.0a0' - libgcc-ng: '>=12' - mpfr: '>=4.1.0,<5.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-hfe3b2da_0.conda - hash: - md5: 289c71e83dc0daa7d4c81f04180778ca - sha256: 2f88965949ba7b4b21e7e5facd62285f7c6efdb17359d1b365c3bb4ecc968d29 - category: main - optional: false -- name: mpfr - version: 4.2.1 - manager: conda - platform: linux-64 - dependencies: - gmp: '>=6.3.0,<7.0a0' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h9458935_1.conda - hash: - md5: 8083b20f566639c22f78bcd6ca35b276 - sha256: 38c501f6b8dff124e57711c01da23e204703a3c14276f4cf6abd28850b2b9893 - category: main - optional: false -- name: mpmath - version: 1.3.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_0.conda - hash: - md5: dbf6e2d89137da32fa6670f3bffc024e - sha256: a4f025c712ec1502a55c471b56a640eaeebfce38dd497d5a1a33729014cac47a - category: main - optional: false -- name: multidict - version: 6.0.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/multidict-6.0.5-py311h459d7ec_0.conda - hash: - md5: 4288ea5cbe686d1b18fc3efb36c009a5 - sha256: aa20fb2d8ecb16099126ec5607fc12082de4111b5e4882e944f4b6cd846178d9 - category: main - optional: false -- name: multiprocess - version: 0.70.16 - manager: conda - platform: linux-64 - dependencies: - dill: '>=0.3.8' - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/multiprocess-0.70.16-py311h459d7ec_0.conda - hash: - md5: b97ca422458b9a0300d73b372d2900d6 - sha256: 04e1fbf003b2c0162afa3c099f5918af7d524bc2300fa5895c37b19881de48b3 - category: main - optional: false -- name: ncurses - version: '6.5' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h59595ed_0.conda - hash: - md5: fcea371545eda051b6deafb24889fc69 - sha256: 4fc3b384f4072b68853a0013ea83bdfd3d66b0126e2238e1d6e1560747aa7586 - category: main - optional: false -- name: nettle - version: 3.9.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/nettle-3.9.1-h7ab15ed_0.conda - hash: - md5: 2bf1915cc107738811368afcb0993a59 - sha256: 1ef1b7efa69c7fb4e2a36a88316f307c115713698d1c12e19f55ae57c0482995 - category: main - optional: false -- name: networkx - version: '3.3' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.10' - url: https://conda.anaconda.org/conda-forge/noarch/networkx-3.3-pyhd8ed1ab_1.conda - hash: - md5: d335fd5704b46f4efb89a6774e81aef0 - sha256: cbd8a6de87ad842e7665df38dcec719873fe74698bc761de5431047b8fada41a - category: main - optional: false -- name: numpy - version: 1.26.4 - manager: conda - platform: linux-64 - dependencies: - libblas: '>=3.9.0,<4.0a0' - libcblas: '>=3.9.0,<4.0a0' - libgcc-ng: '>=12' - liblapack: '>=3.9.0,<4.0a0' - libstdcxx-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py311h64a7726_0.conda - hash: - md5: a502d7aad449a1206efb366d6a12c52d - sha256: 3f4365e11b28e244c95ba8579942b0802761ba7bb31c026f50d1a9ea9c728149 - category: main - optional: false -- name: ocl-icd - version: 2.3.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/ocl-icd-2.3.2-hd590300_1.conda - hash: - md5: c66f837ac65e4d1cdeb80e2a1d5fcc3d - sha256: 0e01384423e48e5011eb6b224da8dc5e3567c87dbcefbe60cd9d5cead276cdcd - category: main - optional: false -- name: onnx - version: 1.16.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - numpy: '>=1.19,<3' - protobuf: '' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - typing-extensions: '>=3.6.2.1' - url: https://conda.anaconda.org/conda-forge/linux-64/onnx-1.16.1-py311h0511f7a_0.conda - hash: - md5: ca7c598c02747d39108434fa359b500c - sha256: 78afaf089c0f9e6898e6bc57a93cc8d96051903238ab890985c81a0a491d043d - category: main - optional: false -- name: onnxruntime - version: 1.18.1 - manager: conda - platform: linux-64 - dependencies: - __cuda: '' - __glibc: '>=2.17,<3.0.a0' - coloredlogs: '' - cudatoolkit: '>=11.8,<12' - cudnn: '>=8.9.7.29,<9.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.19,<3' - packaging: '' - protobuf: '' - python: '>=3.11,<3.12.0a0' - python-flatbuffers: '' - python_abi: 3.11.* - sympy: '' - url: https://conda.anaconda.org/conda-forge/linux-64/onnxruntime-1.18.1-py311hfed4f2b_200_cuda.conda - hash: - md5: 977dddca1ea76687b01176fc7a43a3e4 - sha256: 82d634d9d8aa5ea7de74c603f423298afe24497fb2272b02cc02caa0441b7a75 - category: main - optional: false -- name: open-clip-torch - version: 2.26.1 - manager: conda - platform: linux-64 - dependencies: - ftfy: '' - huggingface_hub: '' - protobuf: '' - python: '>=3.7' - pytorch: '>=1.9.0' - regex: '' - sentencepiece: '' - timm: '' - torchvision: '' - tqdm: '' - url: https://conda.anaconda.org/conda-forge/noarch/open-clip-torch-2.26.1-pyhd8ed1ab_0.conda - hash: - md5: 56316efd5ec141ac8005700e71947eff - sha256: 5a3bf446bc047ec06ce895afa77bd36999432c66a7296e520c1f66a2b53bee32 - category: main - optional: false -- name: openh264 - version: 2.4.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/openh264-2.4.1-h59595ed_0.conda - hash: - md5: 3dfcf61b8e78af08110f5229f79580af - sha256: 0d4eaf15fb771f25c924aef831d76eea11d90c824778fc1e7666346e93475f42 - category: main - optional: false -- name: openjpeg - version: 2.5.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libpng: '>=1.6.43,<1.7.0a0' - libstdcxx-ng: '>=12' - libtiff: '>=4.6.0,<4.7.0a0' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.2-h488ebb8_0.conda - hash: - md5: 7f2e286780f072ed750df46dc2631138 - sha256: 5600a0b82df042bd27d01e4e687187411561dfc11cc05143a08ce29b64bf2af2 - category: main - optional: false -- name: openssl - version: 3.3.1 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - ca-certificates: '' - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.1-h4bc722e_2.conda - hash: - md5: e1b454497f9f7c1147fdde4b53f1b512 - sha256: b294b3cc706ad1048cdb514f0db3da9f37ae3fcc0c53a7104083dd0918adb200 - category: main - optional: false -- name: orc - version: 2.0.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - lz4-c: '>=1.9.3,<1.10.0a0' - snappy: '>=1.2.0,<1.3.0a0' - tzdata: '' - zstd: '>=1.5.6,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/orc-2.0.1-h17fec99_1.conda - hash: - md5: 3bf65f0d8e7322a1cfe8b670fa35ec81 - sha256: d340c67b23fb0e1ef7e13574dd4a428f360bfce93b2a588b3b63625926b038d6 - category: main - optional: false -- name: orjson - version: 3.10.6 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/orjson-3.10.6-py311hb3a8bbb_0.conda - hash: - md5: ef65303adcbcdcf87a35d5120d504896 - sha256: 8bb8bdbf7d930dc3eb1491b65e3cfd7795c0108edcb269ff725d3c7c6cb857ae - category: main - optional: false -- name: p11-kit - version: 0.24.1 - manager: conda - platform: linux-64 - dependencies: - libffi: '>=3.4.2,<3.5.0a0' - libgcc-ng: '>=12' - libtasn1: '>=4.18.0,<5.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/p11-kit-0.24.1-hc5aa10d_0.tar.bz2 - hash: - md5: 56ee94e34b71742bbdfa832c974e47a8 - sha256: aa8d3887b36557ad0c839e4876c0496e0d670afe843bf5bba4a87764b868196d - category: main - optional: false -- name: packaging - version: '24.1' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/packaging-24.1-pyhd8ed1ab_0.conda - hash: - md5: cbe1bb1f21567018ce595d9c2be0f0db - sha256: 36aca948219e2c9fdd6d80728bcc657519e02f06c2703d8db3446aec67f51d81 - category: main - optional: false -- name: pandas - version: 2.2.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - numpy: '>=1.19,<3' - python: '>=3.11,<3.12.0a0' - python-dateutil: '>=2.8.1' - python-tzdata: '>=2022a' - python_abi: 3.11.* - pytz: '>=2020.1' - url: https://conda.anaconda.org/conda-forge/linux-64/pandas-2.2.2-py311h14de704_1.conda - hash: - md5: 84e2dd379d4edec4dd6382861486104d - sha256: d600c0cc42fca1ad36d969758b2495062ad83124ecfcf5673c98b11093af7055 - category: main - optional: false -- name: pcre2 - version: '10.44' - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - libgcc-ng: '>=12' - libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.44-h0f59acf_0.conda - hash: - md5: 3914f7ac1761dce57102c72ca7c35d01 - sha256: 90646ad0d8f9d0fd896170c4f3d754e88c4ba0eaf856c24d00842016f644baab - category: main - optional: false -- name: pillow - version: 10.4.0 - manager: conda - platform: linux-64 - dependencies: - freetype: '>=2.12.1,<3.0a0' - lcms2: '>=2.16,<3.0a0' - libgcc-ng: '>=12' - libjpeg-turbo: '>=3.0.0,<4.0a0' - libtiff: '>=4.6.0,<4.7.0a0' - libwebp-base: '>=1.4.0,<2.0a0' - libxcb: '>=1.16,<1.17.0a0' - libzlib: '>=1.3.1,<2.0a0' - openjpeg: '>=2.5.2,<3.0a0' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - tk: '>=8.6.13,<8.7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pillow-10.4.0-py311h82a398c_0.conda - hash: - md5: b9e0ac1f5564b6572a6d702c04207be8 - sha256: baad77ac48dab88863c072bb47697161bc213c926cb184f4053b8aa5b467f39b - category: main - optional: false -- name: pip - version: '24.0' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - setuptools: '' - wheel: '' - url: https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda - hash: - md5: f586ac1e56c8638b64f9c8122a7b8a67 - sha256: b7c1c5d8f13e8cb491c4bd1d0d1896a4cf80fc47de01059ad77509112b664a4a - category: main - optional: false -- name: pixman - version: 0.43.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.43.2-h59595ed_0.conda - hash: - md5: 71004cbf7924e19c02746ccde9fd7123 - sha256: 366d28e2a0a191d6c535e234741e0cd1d94d713f76073d8af4a5ccb2a266121e - category: main - optional: false -- name: protobuf - version: 4.25.3 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20240116.1,<20240117.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libstdcxx-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - setuptools: '' - url: https://conda.anaconda.org/conda-forge/linux-64/protobuf-4.25.3-py311h7b78aeb_0.conda - hash: - md5: fe6c263e6bd0ec098995b7cd176b0f95 - sha256: 90eccef0b175777de1d179fc66e47af47ad0ae2bb9a949a08cc1d42b8b1ec57f - category: main - optional: false -- name: pthread-stubs - version: '0.4' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=7.5.0' - url: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2 - hash: - md5: 22dad4df6e8630e8dff2428f6f6a7036 - sha256: 67c84822f87b641d89df09758da498b2d4558d47b920fd1d3fe6d3a871e000ff - category: main - optional: false -- name: pugixml - version: '1.14' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/pugixml-1.14-h59595ed_0.conda - hash: - md5: 2c97dd90633508b422c11bd3018206ab - sha256: ea5f2d593177318f6b19af05018c953f41124cbb3bf21f9fdedfdb6ac42913ae - category: main - optional: false -- name: pyarrow - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - libarrow-acero: 17.0.0.* - libarrow-dataset: 17.0.0.* - libarrow-substrait: 17.0.0.* - libparquet: 17.0.0.* - numpy: '>=1.19,<3' - pyarrow-core: 17.0.0 - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-17.0.0-py311hbd00459_0.conda - hash: - md5: c662eca4227bb0fdd607fcc4abba5b52 - sha256: 04947956a76842f748a74d053629777b268aaf886fc4c527bcd0ae930fbdce00 - category: main - optional: false -- name: pyarrow-core - version: 17.0.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libarrow: 17.0.0.* - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.3.1,<2.0a0' - numpy: '>=1.19,<3' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-17.0.0-py311h9460f28_0_cpu.conda - hash: - md5: 4118e8d8389f42935d91559b338f387e - sha256: a1c1a6a056b531b042b7fbdc8cac8a3ec734bf15a5377231c1d5cd6cdd8768b3 - category: main - optional: false -- name: pyarrow-hotfix - version: '0.6' - manager: conda - platform: linux-64 - dependencies: - pyarrow: '>=0.14' - python: '>=3.5' - url: https://conda.anaconda.org/conda-forge/noarch/pyarrow-hotfix-0.6-pyhd8ed1ab_0.conda - hash: - md5: ccc06e6ef2064ae129fab3286299abda - sha256: 9b767969d059c106aac6596438a7e7ebd3aa1e2ff6553d4b7e05126dfebf4bd6 - category: main - optional: false -- name: pycparser - version: '2.22' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda - hash: - md5: 844d9eb3b43095b031874477f7d70088 - sha256: 406001ebf017688b1a1554b49127ca3a4ac4626ec0fd51dc75ffa4415b720b64 - category: main - optional: false -- name: pygments - version: 2.18.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_0.conda - hash: - md5: b7f5c092b8f9800150d998a71b76d5a1 - sha256: 78267adf4e76d0d64ea2ffab008c501156c108bb08fecb703816fb63e279780b - category: main - optional: false -- name: pysocks - version: 1.7.1 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - hash: - md5: 2a7de29fb590ca14b5243c4c812c8025 - sha256: a42f826e958a8d22e65b3394f437af7332610e43ee313393d1cf143f0a2d274b - category: main - optional: false -- name: python - version: 3.11.9 - manager: conda - platform: linux-64 - dependencies: - bzip2: '>=1.0.8,<2.0a0' - ld_impl_linux-64: '>=2.36.1' - libexpat: '>=2.6.2,<3.0a0' - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - libnsl: '>=2.0.1,<2.1.0a0' - libsqlite: '>=3.45.3,<4.0a0' - libuuid: '>=2.38.1,<3.0a0' - libxcrypt: '>=4.4.36' - libzlib: '>=1.2.13,<2.0.0a0' - ncurses: '>=6.4.20240210,<7.0a0' - openssl: '>=3.2.1,<4.0a0' - readline: '>=8.2,<9.0a0' - tk: '>=8.6.13,<8.7.0a0' - tzdata: '' - xz: '>=5.2.6,<6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/python-3.11.9-hb806964_0_cpython.conda - hash: - md5: ac68acfa8b558ed406c75e98d3428d7b - sha256: 177f33a1fb8d3476b38f73c37b42f01c0b014fa0e039a701fd9f83d83aae6d40 - category: main - optional: false -- name: python-dateutil - version: 2.9.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - six: '>=1.5' - url: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda - hash: - md5: 2cf4264fffb9e6eff6031c5b6884d61c - sha256: f3ceef02ac164a8d3a080d0d32f8e2ebe10dd29e3a685d240e38b3599e146320 - category: main - optional: false -- name: python-flatbuffers - version: 24.3.25 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-flatbuffers-24.3.25-pyh59ac667_0.conda - hash: - md5: dfc884dcd61ff6543fde37a41b7d7f31 - sha256: 6a9d285fef959480eccbc69e276ede64e292c8eee35ddc727d5a0fb9a4bcc3a2 - category: main - optional: false -- name: python-tzdata - version: '2024.1' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.6' - url: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2024.1-pyhd8ed1ab_0.conda - hash: - md5: 98206ea9954216ee7540f0c773f2104d - sha256: 9da9a849d53705dee450b83507df1ca8ffea5f83bd21a215202221f1c492f8ad - category: main - optional: false -- name: python-xxhash - version: 3.4.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - xxhash: '>=0.8.2,<0.8.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.4.1-py311h459d7ec_0.conda - hash: - md5: 60b5332b3989fda37884b92c7afd6a91 - sha256: 91293b2ca0f36ac580f2be4b9c0858cdaec52eff95473841231dcd044acd2e12 - category: main - optional: false -- name: python_abi - version: '3.11' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.11-4_cp311.conda - hash: - md5: d786502c97404c94d7d58d258a445a65 - sha256: 0be3ac1bf852d64f553220c7e6457e9c047dfb7412da9d22fbaa67e60858b3cf - category: main - optional: false -- name: pytorch - version: 2.4.0 - manager: conda - platform: linux-64 - dependencies: - blas: '*' - filelock: '' - jinja2: '' - llvm-openmp: <16 - mkl: '>=2018' - networkx: '' - python: '>=3.11,<3.12.0a0' - pytorch-cuda: '>=12.4,<12.5' - pytorch-mutex: '1.0' - pyyaml: '' - sympy: '' - torchtriton: 3.0.0 - typing_extensions: '' - url: https://conda.anaconda.org/pytorch/linux-64/pytorch-2.4.0-py3.11_cuda12.4_cudnn9.1.0_0.tar.bz2 - hash: - md5: fbf023c0a2c2573aa9ff0c727410fff5 - sha256: c8c52b47ccec2ade63bf76e398d86bed0723936fd5713192019edd4f33cb577d - category: main - optional: false -- name: pytorch-cuda - version: '12.4' - manager: conda - platform: linux-64 - dependencies: - cuda-cudart: '>=12.4,<12.5' - cuda-cupti: '>=12.4,<12.5' - cuda-libraries: '>=12.4,<12.5' - cuda-nvrtc: '>=12.4,<12.5' - cuda-nvtx: '>=12.4,<12.5' - cuda-runtime: '>=12.4,<12.5' - libcublas: '>=12.4.2.65,<12.4.5.8' - libcufft: '>=11.2.0.44,<11.2.1.3' - libcusolver: '>=11.6.0.99,<11.6.1.9' - libcusparse: '>=12.3.0.142,<12.3.1.170' - libnpp: '>=12.2.5.2,<12.2.5.30' - libnvjitlink: '>=12.4.99,<12.4.127' - libnvjpeg: '>=12.3.1.89,<12.3.1.117' - url: https://conda.anaconda.org/pytorch/linux-64/pytorch-cuda-12.4-hc786d27_6.tar.bz2 - hash: - md5: 294df2aee019b0e314713842d46e6b65 - sha256: fb74f81c75392c58cad8ff9c5a3366f8224e4d9cb77501cb50f7abe39e1a2ddb - category: main - optional: false -- name: pytorch-mutex - version: '1.0' - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/pytorch/noarch/pytorch-mutex-1.0-cuda.tar.bz2 - hash: - md5: a948316e36fb5b11223b3fcfa93f8358 - sha256: c16316183f51b74ca5eee4dcb8631052f328c0bbf244176734a0b7d390b81ee3 - category: main - optional: false -- name: pytz - version: '2024.1' - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/pytz-2024.1-pyhd8ed1ab_0.conda - hash: - md5: 3eeeeb9e4827ace8c0c1419c85d590ad - sha256: 1a7d6b233f7e6e3bbcbad054c8fd51e690a67b129a899a056a5e45dd9f00cb41 - category: main - optional: false -- name: pyyaml - version: 6.0.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - yaml: '>=0.2.5,<0.3.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.1-py311h459d7ec_1.conda - hash: - md5: 52719a74ad130de8fb5d047dc91f247a - sha256: 28729ef1ffa7f6f9dfd54345a47c7faac5d34296d66a2b9891fb147f4efe1348 - category: main - optional: false -- name: re2 - version: 2023.09.01 - manager: conda - platform: linux-64 - dependencies: - libre2-11: 2023.09.01 - url: https://conda.anaconda.org/conda-forge/linux-64/re2-2023.09.01-h7f4b329_2.conda - hash: - md5: 8f70e36268dea8eb666ef14c29bd3cda - sha256: f0f520f57e6b58313e8c41abc7dfa48742a05f1681f05654558127b667c769a8 - category: main - optional: false -- name: readline - version: '8.2' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - ncurses: '>=6.3,<7.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - hash: - md5: 47d31b792659ce70f470b5c82fdfb7a4 - sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 - category: main - optional: false -- name: regex - version: 2024.7.24 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/regex-2024.7.24-py311h61187de_0.conda - hash: - md5: 090222c7863ad3fe208a35998b81e5df - sha256: f428f93fd67b7b14acdb535c39699c3e3d9af215c0b484ae13d143905d059bf3 - category: main - optional: false -- name: requests - version: 2.32.3 - manager: conda - platform: linux-64 - dependencies: - certifi: '>=2017.4.17' - charset-normalizer: '>=2,<4' - idna: '>=2.5,<4' - python: '>=3.8' - urllib3: '>=1.21.1,<3' - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda - hash: - md5: 5ede4753180c7a550a443c430dc8ab52 - sha256: 5845ffe82a6fa4d437a2eae1e32a1ad308d7ad349f61e337c0a890fe04c513cc - category: main - optional: false -- name: rich - version: 13.7.1 - manager: conda - platform: linux-64 - dependencies: - markdown-it-py: '>=2.2.0' - pygments: '>=2.13.0,<3.0.0' - python: '>=3.7.0' - typing_extensions: '>=4.0.0,<5.0.0' - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.7.1-pyhd8ed1ab_0.conda - hash: - md5: ba445bf767ae6f0d959ff2b40c20912b - sha256: 2b26d58aa59e46f933c3126367348651b0dab6e0bf88014e857415bb184a4667 - category: main - optional: false -- name: s2n - version: 1.4.17 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - openssl: '>=3.3.1,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/s2n-1.4.17-he19d79f_0.conda - hash: - md5: e25ac9bf10f8e6aa67727b1cdbe762ef - sha256: 6d1aa582964771a6cf47d120e2c5cdc700fe3744101cd5660af1eb81d47d689a - category: main - optional: false -- name: safetensors - version: 0.4.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/safetensors-0.4.3-py311h46250e7_0.conda - hash: - md5: b8b856dca5eb2317f6968e4b6b3e09c5 - sha256: 4988d6c8636f37d1e5c831c08b5ca5060a3499989031369604c7aa08e3990455 - category: main - optional: false -- name: sentencepiece - version: 0.2.0 - manager: conda - platform: linux-64 - dependencies: - libsentencepiece: 0.2.0 - python_abi: 3.11.* - sentencepiece-python: 0.2.0 - sentencepiece-spm: 0.2.0 - url: https://conda.anaconda.org/conda-forge/linux-64/sentencepiece-0.2.0-h38be061_2.conda - hash: - md5: 3071ca26573aac7def93bb02934d077b - sha256: d77df3309eaa8ae5be61c919ccd2061fcc208c64172bb103ae0caf44f6fd6506 - category: main - optional: false -- name: sentencepiece-python - version: 0.2.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libsentencepiece: 0.2.0 - libstdcxx-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/sentencepiece-python-0.2.0-py311h7fa642f_2.conda - hash: - md5: 60ded67bfefb7f358f01a52556c79dfe - sha256: 4ecd2790bccf92e1b1b462ec78abbc17ddf7cd069672cfd9222b1b0ca7e07931 - category: main - optional: false -- name: sentencepiece-spm - version: 0.2.0 - manager: conda - platform: linux-64 - dependencies: - libabseil: '>=20240116.2,<20240117.0a0' - libgcc-ng: '>=12' - libprotobuf: '>=4.25.3,<4.25.4.0a0' - libsentencepiece: 0.2.0 - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/sentencepiece-spm-0.2.0-he81a138_2.conda - hash: - md5: 153728c1e224f44390004b2f9666f1a8 - sha256: 3adf3798bd788192315710e68275d34ce0553a615a9844ae24942ed08f06dcd4 - category: main - optional: false -- name: setuptools - version: 68.2.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-68.2.2-pyhd8ed1ab_0.conda - hash: - md5: fc2166155db840c634a1291a5c35a709 - sha256: 851901b1f8f2049edb36a675f0c3f9a98e1495ef4eb214761b048c6f696a06f7 - category: main - optional: false -- name: six - version: 1.16.0 - manager: conda - platform: linux-64 - dependencies: - python: '' - url: https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2 - hash: - md5: e5f25f8dbc060e9a8d912e432202afc2 - sha256: a85c38227b446f42c5b90d9b642f2c0567880c15d72492d8da074a59c8f91dd6 - category: main - optional: false -- name: snappy - version: 1.2.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/snappy-1.2.1-ha2e4443_0.conda - hash: - md5: 6b7dcc7349efd123d493d2dbe85a045f - sha256: dc7c8e0e8c3e8702aae81c52d940bfaabe756953ee51b1f1757e891bab62cf7f - category: main - optional: false -- name: svt-av1 - version: 2.1.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/svt-av1-2.1.2-hac33072_0.conda - hash: - md5: 06c5dec4ebb47213b648a6c4dc8400d6 - sha256: 3077a32687c6ccf853c978ad97b77a08fc518c94e73eb449f5a312f1d77d33f0 - category: main - optional: false -- name: sympy - version: 1.13.0 - manager: conda - platform: linux-64 - dependencies: - __unix: '' - gmpy2: '>=2.0.8' - mpmath: '>=0.19' - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.0-pypyh2585a3b_103.conda - hash: - md5: be7ad175eb670a83ff575f86e53c57fb - sha256: dcb51a1e46a2777c76098b558bd05f107647ab0a03a1560445620ecb14a51c4f - category: main - optional: false -- name: sysroot_linux-64 - version: '2.17' - manager: conda - platform: linux-64 - dependencies: - _sysroot_linux-64_curr_repodata_hack: 3.* - kernel-headers_linux-64: 3.10.0 - tzdata: '' - url: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h4a8ded7_16.conda - hash: - md5: 223fe8a3ff6d5e78484a9d58eb34d055 - sha256: b892b0b9c6dc8efe8b9b5442597d1ab8d65c0dc7e4e5a80f822cbdf0a639bd77 - category: main - optional: false -- name: tbb - version: 2021.12.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - libgcc-ng: '>=12' - libhwloc: '>=2.11.1,<2.11.2.0a0' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.12.0-h434a139_3.conda - hash: - md5: c667c11d1e488a38220ede8a34441bff - sha256: e901e1887205a3f90d6a77e1302ccc5ffe48fd30de16907dfdbdbf1dbef0a177 - category: main - optional: false -- name: timm - version: 1.0.7 - manager: conda - platform: linux-64 - dependencies: - huggingface_hub: '' - python: '>=3.8' - pytorch: '>=1.7' - pyyaml: '' - safetensors: '>=0.2' - torchvision: '' - url: https://conda.anaconda.org/conda-forge/noarch/timm-1.0.7-pyhd8ed1ab_0.conda - hash: - md5: ed57a61215da191e22ea97abde77158f - sha256: c9fdbb759dcd4c6b9b74236ad51f92ba002900bfecef898a74c14dad5f51445e - category: main - optional: false -- name: tk - version: 8.6.13 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - hash: - md5: d453b98d9c83e71da0741bb0ff4d76bc - sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e - category: main - optional: false -- name: tokenizers - version: 0.19.1 - manager: conda - platform: linux-64 - dependencies: - huggingface_hub: '>=0.16.4,<1.0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - openssl: '>=3.2.1,<4.0a0' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/tokenizers-0.19.1-py311h6640629_0.conda - hash: - md5: ce36ac97a7943e84d1c03e587785c671 - sha256: 99b14e2581e54d413a0d771bcf783e160df042bd983c9eed808746b5e4b178e1 - category: main - optional: false -- name: torchtriton - version: 3.0.0 - manager: conda - platform: linux-64 - dependencies: - filelock: '' - python: '>=3.11,<3.12.0a0' - pytorch: '' - url: https://conda.anaconda.org/pytorch/linux-64/torchtriton-3.0.0-py311.tar.bz2 - hash: - md5: 8e4e3425c16b41842b23490ae4f267b8 - sha256: 0c965cd1c12b728b2c9bc1dd390a7953626d0665bc009d2e35850e5db5d394d5 - category: main - optional: false -- name: torchvision - version: 0.19.0 - manager: conda - platform: linux-64 - dependencies: - ffmpeg: '>=4.2' - libjpeg-turbo: '' - libpng: '' - numpy: '>=1.23.5' - pillow: '>=5.3.0,!=8.3.*' - python: '>=3.11,<3.12.0a0' - pytorch: 2.4.0 - pytorch-cuda: 12.4.* - pytorch-mutex: '1.0' - requests: '' - url: https://conda.anaconda.org/pytorch/linux-64/torchvision-0.19.0-py311_cu124.tar.bz2 - hash: - md5: 9fabed389795ec65c004408ea928c4da - sha256: 8102ade7f5a97eff811fa1a1ea64ac2903d58c59df45daa0df951308bcfd26b9 - category: main - optional: false -- name: tqdm - version: 4.66.4 - manager: conda - platform: linux-64 - dependencies: - colorama: '' - python: '>=3.7' - url: https://conda.anaconda.org/conda-forge/noarch/tqdm-4.66.4-pyhd8ed1ab_0.conda - hash: - md5: e74cd796e70a4261f86699ee0a3a7a24 - sha256: 75342f40a69e434a1a23003c3e254a95dca695fb14955bc32f1819cd503964b2 - category: main - optional: false -- name: transformers - version: 4.43.3 - manager: conda - platform: linux-64 - dependencies: - datasets: '!=2.5.0' - filelock: '' - huggingface_hub: '>=0.23.0,<1.0' - numpy: '>=1.17,<2.0' - packaging: '>=20.0' - python: '>=3.8' - pyyaml: '>=5.1' - regex: '!=2019.12.17' - requests: '' - safetensors: '>=0.4.1' - tokenizers: '>=0.19,<0.20' - tqdm: '>=4.27' - url: https://conda.anaconda.org/conda-forge/noarch/transformers-4.43.3-pyhd8ed1ab_0.conda - hash: - md5: 4faf00d692b1accb08a65518d8c79c2c - sha256: 2c809367c20c1e89630fbcf7c5bdfc829d4eed39bf5ebc5ef6139b275d79114f - category: main - optional: false -- name: typing-extensions - version: 4.12.2 - manager: conda - platform: linux-64 - dependencies: - typing_extensions: 4.12.2 - url: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.12.2-hd8ed1ab_0.conda - hash: - md5: 52d648bd608f5737b123f510bb5514b5 - sha256: d3b9a8ed6da7c9f9553c5fd8a4fca9c3e0ab712fa5f497859f82337d67533b73 - category: main - optional: false -- name: typing_extensions - version: 4.12.2 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_0.conda - hash: - md5: ebe6952715e1d5eb567eeebf25250fa7 - sha256: 0fce54f8ec3e59f5ef3bb7641863be4e1bf1279623e5af3d3fa726e8f7628ddb - category: main - optional: false -- name: tzdata - version: 2024a - manager: conda - platform: linux-64 - dependencies: {} - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda - hash: - md5: 161081fc7cec0bfda0d86d7cb595f8d8 - sha256: 7b2b69c54ec62a243eb6fba2391b5e443421608c3ae5dbff938ad33ca8db5122 - category: main - optional: false -- name: urllib3 - version: 2.2.2 - manager: conda - platform: linux-64 - dependencies: - brotli-python: '>=1.0.9' - h2: '>=4,<5' - pysocks: '>=1.5.6,<2.0,!=1.5.7' - python: '>=3.8' - zstandard: '>=0.18.0' - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_1.conda - hash: - md5: e804c43f58255e977093a2298e442bb8 - sha256: 00c47c602c03137e7396f904eccede8cc64cc6bad63ce1fc355125df8882a748 - category: main - optional: false -- name: wayland - version: 1.23.0 - manager: conda - platform: linux-64 - dependencies: - libexpat: '>=2.6.2,<3.0a0' - libffi: '>=3.4,<4.0a0' - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.23.0-h5291e77_0.conda - hash: - md5: c13ca0abd5d1d31d0eebcf86d51da8a4 - sha256: 5f2572290dd09d5480abe6e0d9635c17031a12fd4e68578680e9f49444d6dd8b - category: main - optional: false -- name: wayland-protocols - version: '1.36' - manager: conda - platform: linux-64 - dependencies: - wayland: '' - url: https://conda.anaconda.org/conda-forge/noarch/wayland-protocols-1.36-hd8ed1ab_0.conda - hash: - md5: c6f690e7d4abf562161477f14533cfd8 - sha256: ee18ec691d0c80b9493ba064930c1fedb8e7c369285ca78f7a39ecc4af908410 - category: main - optional: false -- name: wcwidth - version: 0.2.13 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda - hash: - md5: 68f0738df502a14213624b288c60c9ad - sha256: b6cd2fee7e728e620ec736d8dfee29c6c9e2adbd4e695a31f1d8f834a83e57e3 - category: main - optional: false -- name: wheel - version: 0.43.0 - manager: conda - platform: linux-64 - dependencies: - python: '>=3.8' - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.43.0-pyhd8ed1ab_1.conda - hash: - md5: 0b5293a157c2b5cd513dd1b03d8d3aae - sha256: cb318f066afd6fd64619f14c030569faf3f53e6f50abf743b4c865e7d95b96bc - category: main - optional: false -- name: x264 - version: 1!164.3095 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/x264-1!164.3095-h166bdaf_2.tar.bz2 - hash: - md5: 6c99772d483f566d59e25037fea2c4b1 - sha256: 175315eb3d6ea1f64a6ce470be00fa2ee59980108f246d3072ab8b977cb048a5 - category: main - optional: false -- name: x265 - version: '3.5' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=10.3.0' - libstdcxx-ng: '>=10.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/x265-3.5-h924138e_3.tar.bz2 - hash: - md5: e7f6ed84d4623d52ee581325c1587a6b - sha256: 76c7405bcf2af639971150f342550484efac18219c0203c5ee2e38b8956fe2a0 - category: main - optional: false -- name: xorg-fixesproto - version: '5.0' - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - xorg-xextproto: '' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-fixesproto-5.0-h7f98852_1002.tar.bz2 - hash: - md5: 65ad6e1eb4aed2b0611855aff05e04f6 - sha256: 5d2af1b40f82128221bace9466565eca87c97726bb80bbfcd03871813f3e1876 - category: main - optional: false -- name: xorg-kbproto - version: 1.0.7 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2 - hash: - md5: 4b230e8381279d76131116660f5a241a - sha256: e90b0a6a5d41776f11add74aa030f789faf4efd3875c31964d6f9cfa63a10dd1 - category: main - optional: false -- name: xorg-libice - version: 1.1.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.1-hd590300_0.conda - hash: - md5: b462a33c0be1421532f28bfe8f4a7514 - sha256: 5aa9b3682285bb2bf1a8adc064cb63aff76ef9178769740d855abb42b0d24236 - category: main - optional: false -- name: xorg-libsm - version: 1.2.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libuuid: '>=2.38.1,<3.0a0' - xorg-libice: '>=1.1.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.4-h7391055_0.conda - hash: - md5: 93ee23f12bc2e684548181256edd2cf6 - sha256: 089ad5f0453c604e18985480218a84b27009e9e6de9a0fa5f4a20b8778ede1f1 - category: main - optional: false -- name: xorg-libx11 - version: 1.8.9 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libxcb: '>=1.16,<1.17.0a0' - xorg-kbproto: '' - xorg-xextproto: '>=7.3.0,<8.0a0' - xorg-xproto: '' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.9-hb711507_1.conda - hash: - md5: 4a6d410296d7e39f00bacdee7df046e9 - sha256: 66eabe62b66c1597c4a755dcd3f4ce2c78adaf7b32e25dfee45504d67d7735c1 - category: main - optional: false -- name: xorg-libxau - version: 1.0.11 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.11-hd590300_0.conda - hash: - md5: 2c80dc38fface310c9bd81b17037fee5 - sha256: 309751371d525ce50af7c87811b435c176915239fc9e132b99a25d5e1703f2d4 - category: main - optional: false -- name: xorg-libxdmcp - version: 1.1.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2 - hash: - md5: be93aabceefa2fac576e971aef407908 - sha256: 4df7c5ee11b8686d3453e7f3f4aa20ceef441262b49860733066c52cfd0e4a77 - category: main - optional: false -- name: xorg-libxext - version: 1.3.4 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - xorg-libx11: '>=1.7.2,<2.0a0' - xorg-xextproto: '' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h0b41bf4_2.conda - hash: - md5: 82b6df12252e6f32402b96dacc656fec - sha256: 73e5cfbdff41ef8a844441f884412aa5a585a0f0632ec901da035a03e1fe1249 - category: main - optional: false -- name: xorg-libxfixes - version: 5.0.3 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - xorg-fixesproto: '' - xorg-libx11: '>=1.7.0,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-5.0.3-h7f98852_1004.tar.bz2 - hash: - md5: e9a21aa4d5e3e5f1aed71e8cefd46b6a - sha256: 1e426a1abb774ef1dcf741945ed5c42ad12ea2dc7aeed7682d293879c3e1e4c3 - category: main - optional: false -- name: xorg-libxrender - version: 0.9.11 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - xorg-libx11: '>=1.8.6,<2.0a0' - xorg-renderproto: '' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.11-hd590300_0.conda - hash: - md5: ed67c36f215b310412b2af935bf3e530 - sha256: 26da4d1911473c965c32ce2b4ff7572349719eaacb88a066db8d968a4132c3f7 - category: main - optional: false -- name: xorg-renderproto - version: 0.11.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2 - hash: - md5: 06feff3d2634e3097ce2fe681474b534 - sha256: 38942930f233d1898594dd9edf4b0c0786f3dbc12065a0c308634c37fd936034 - category: main - optional: false -- name: xorg-xextproto - version: 7.3.0 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h0b41bf4_1003.conda - hash: - md5: bce9f945da8ad2ae9b1d7165a64d0f87 - sha256: b8dda3b560e8a7830fe23be1c58cc41f407b2e20ae2f3b6901eb5842ba62b743 - category: main - optional: false -- name: xorg-xproto - version: 7.0.31 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.3.0' - url: https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2 - hash: - md5: b4a4381d54784606820704f7b5f05a15 - sha256: f197bb742a17c78234c24605ad1fe2d88b1d25f332b75d73e5ba8cf8fbc2a10d - category: main - optional: false -- name: xxhash - version: 0.8.2 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.2-hd590300_0.conda - hash: - md5: f08fb5c89edfc4aadee1c81d4cfb1fa1 - sha256: 6fe74a8fd84ab0dc25e4dc3e0c22388dd8accb212897a208b14fe5d4fbb8fc2f - category: main - optional: false -- name: xz - version: 5.2.6 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - url: https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2 - hash: - md5: 2161070d867d1b1204ea749c8eec4ef0 - sha256: 03a6d28ded42af8a347345f82f3eebdd6807a08526d47899a42d62d319609162 - category: main - optional: false -- name: yaml - version: 0.2.5 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=9.4.0' - url: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - hash: - md5: 4cb3ad778ec2d5a7acbdf254eb1c42ae - sha256: a4e34c710eeb26945bdbdaba82d3d74f60a78f54a874ec10d373811a5d217535 - category: main - optional: false -- name: yarl - version: 1.9.4 - manager: conda - platform: linux-64 - dependencies: - idna: '>=2.0' - libgcc-ng: '>=12' - multidict: '>=4.0' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/yarl-1.9.4-py311h459d7ec_0.conda - hash: - md5: fff0f2058e9d86c8bf5848ee93917a8d - sha256: 673e4a626e9e7d661154e5609f696c0c8a9247087f5c8b7744cfbb4fe0872713 - category: main - optional: false -- name: zlib - version: 1.3.1 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libzlib: 1.3.1 - url: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-h4ab18f5_1.conda - hash: - md5: 9653f1bf3766164d0e65fa723cabbc54 - sha256: cee16ab07a11303de721915f0a269e8c7a54a5c834aa52f74b1cc3a59000ade8 - category: main - optional: false -- name: zstandard - version: 0.23.0 - manager: conda - platform: linux-64 - dependencies: - __glibc: '>=2.17,<3.0.a0' - cffi: '>=1.11' - libgcc-ng: '>=12' - python: '>=3.11,<3.12.0a0' - python_abi: 3.11.* - zstd: '>=1.5.6,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py311h5cd10c7_0.conda - hash: - md5: 8efe4fe2396281627b3450af8357b190 - sha256: ee4e7202ed6d6027eabb9669252b4dfd8144d4fde644435ebe39ab608086e7af - category: main - optional: false -- name: zstd - version: 1.5.6 - manager: conda - platform: linux-64 - dependencies: - libgcc-ng: '>=12' - libstdcxx-ng: '>=12' - libzlib: '>=1.2.13,<2.0.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda - hash: - md5: 4d056880988120e29d75bfff282e0f45 - sha256: c558b9cc01d9c1444031bd1ce4b9cff86f9085765f17627a6cd85fc623c8a02b - category: main - optional: false -- name: multilingual-clip - version: 1.0.10 - manager: pip - platform: linux-64 - dependencies: - transformers: '*' - url: https://files.pythonhosted.org/packages/da/f7/575f65ab34993153e9bc88ea5e58d59475bd9f191d2db29729e01c4231f6/multilingual_clip-1.0.10-py3-none-any.whl - hash: - sha256: b9acf95b8309c85a0db5e9c88c5f1b400687e08d72408c460731ae31e71dc73a - category: main - optional: false -- name: onnxsim - version: 0.4.36 - manager: pip - platform: linux-64 - dependencies: - onnx: '*' - rich: '*' - url: https://files.pythonhosted.org/packages/db/94/22aab761b3d416bce02020d9ca98dc692427c2717b0325952e30ce41f83b/onnxsim-0.4.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - hash: - sha256: fa7596e6b806ed19077f7652788a50ee576c172b4d16d421f0593aef1a6fa4c4 - category: main - optional: false diff --git a/machine-learning/export/env.dev.yaml b/machine-learning/export/env.dev.yaml deleted file mode 100644 index 1e78180e39..0000000000 --- a/machine-learning/export/env.dev.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: base -channels: - - conda-forge -platforms: - - linux-64 - - linux-aarch64 -dependencies: - - black - - conda-lock - - mypy - - pytest - - pytest-cov - - pytest-mock - - ruff -category: dev diff --git a/machine-learning/export/env.yaml b/machine-learning/export/env.yaml deleted file mode 100644 index 27fb72098e..0000000000 --- a/machine-learning/export/env.yaml +++ /dev/null @@ -1,25 +0,0 @@ -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 diff --git a/machine-learning/export/immich_model_exporter/__init__.py b/machine-learning/export/immich_model_exporter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/machine-learning/export/immich_model_exporter/export.py b/machine-learning/export/immich_model_exporter/export.py new file mode 100644 index 0000000000..6b9900c5c3 --- /dev/null +++ b/machine-learning/export/immich_model_exporter/export.py @@ -0,0 +1,98 @@ +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 +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. +""" + + +@app.command() +def main( + model_name: str, + model_source: ModelSource, + output_dir: Path = Path("./models"), + no_cache: bool = False, + hf_organization: str = "immich-app", + hf_auth_token: Annotated[str | None, typer.Option(envvar="HF_AUTH_TOKEN")] = None, +) -> None: + 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") + 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, no_cache=no_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, no_cache=no_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 no_cache or not readme_path.exists(): + with open(readme_path, "w") as f: + f.write(generate_readme(model_name, model_source)) + + if hf_auth_token is not None: + from huggingface_hub import create_repo, upload_folder + + repo_id = f"{hf_organization}/{hf_model_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=output_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() + + +if __name__ == "__main__": + typer.run(main) diff --git a/machine-learning/export/immich_model_exporter/exporters/constants.py b/machine-learning/export/immich_model_exporter/exporters/constants.py new file mode 100644 index 0000000000..4019119037 --- /dev/null +++ b/machine-learning/export/immich_model_exporter/exporters/constants.py @@ -0,0 +1,42 @@ +from enum import StrEnum +from typing import NamedTuple + + +class ModelSource(StrEnum): + INSIGHTFACE = "insightface" + MCLIP = "mclip" + OPENCLIP = "openclip" + + +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" + ), +} + +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}", +] diff --git a/machine-learning/export/immich_model_exporter/exporters/onnx/__init__.py b/machine-learning/export/immich_model_exporter/exporters/onnx/__init__.py new file mode 100644 index 0000000000..2bc3ff3952 --- /dev/null +++ b/machine-learning/export/immich_model_exporter/exporters/onnx/__init__.py @@ -0,0 +1,20 @@ +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, no_cache: bool = False +) -> 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, no_cache=no_cache) + case ModelSource.OPENCLIP: + name, _, pretrained = model_name.partition("__") + config = openclip.OpenCLIPModelConfig(name, pretrained) + openclip.to_onnx(config, opset_version, visual_dir, textual_dir, no_cache=no_cache) + case _: + raise ValueError(f"Unsupported model source {model_source}") diff --git a/machine-learning/export/immich_model_exporter/exporters/onnx/models/__init__.py b/machine-learning/export/immich_model_exporter/exporters/onnx/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/machine-learning/export/models/mclip.py b/machine-learning/export/immich_model_exporter/exporters/onnx/models/mclip.py similarity index 69% rename from machine-learning/export/models/mclip.py rename to machine-learning/export/immich_model_exporter/exporters/onnx/models/mclip.py index 06324e490d..ab26ac7614 100644 --- a/machine-learning/export/models/mclip.py +++ b/machine-learning/export/immich_model_exporter/exporters/onnx/models/mclip.py @@ -1,11 +1,6 @@ -import os -import tempfile import warnings from pathlib import Path - -import torch -from multilingual_clip.pt_multilingual_clip import MultilingualCLIP -from transformers import AutoTokenizer +from typing import Any from .openclip import OpenCLIPModelConfig from .openclip import to_onnx as openclip_to_onnx @@ -21,25 +16,40 @@ _MCLIP_TO_OPENCLIP = { def to_onnx( model_name: str, + opset_version: int, output_dir_visual: Path | str, output_dir_textual: Path | str, + no_cache: bool = False, ) -> tuple[Path, Path]: textual_path = get_model_path(output_dir_textual) - with tempfile.TemporaryDirectory() as tmpdir: - model = MultilingualCLIP.from_pretrained(model_name, cache_dir=os.environ.get("CACHE_DIR", tmpdir)) + if no_cache or not textual_path.exists(): + import torch + 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) model.eval() for param in model.parameters(): param.requires_grad_(False) - export_text_encoder(model, textual_path) - visual_path, _ = openclip_to_onnx(_MCLIP_TO_OPENCLIP[model_name], output_dir_visual) - assert visual_path is not None, "Visual model export failed" + _export_text_encoder(model, textual_path, opset_version) + else: + print(f"Model {textual_path} already exists, skipping") + visual_path, _ = openclip_to_onnx( + _MCLIP_TO_OPENCLIP[model_name], opset_version, output_dir_visual, no_cache=no_cache + ) + assert visual_path is not None, "Visual model export failed" return visual_path, textual_path -def export_text_encoder(model: MultilingualCLIP, output_path: Path | str) -> None: +def _export_text_encoder(model: Any, output_path: Path | str, opset_version: int) -> None: + import torch + from multilingual_clip.pt_multilingual_clip import MultilingualCLIP + output_path = Path(output_path) def forward(self: MultilingualCLIP, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor: @@ -61,7 +71,7 @@ def export_text_encoder(model: MultilingualCLIP, output_path: Path | str) -> Non output_path.as_posix(), input_names=["input_ids", "attention_mask"], output_names=["embedding"], - opset_version=17, + opset_version=opset_version, # dynamic_axes={ # "input_ids": {0: "batch_size", 1: "sequence_length"}, # "attention_mask": {0: "batch_size", 1: "sequence_length"}, diff --git a/machine-learning/export/immich_model_exporter/exporters/onnx/models/openclip.py b/machine-learning/export/immich_model_exporter/exporters/onnx/models/openclip.py new file mode 100644 index 0000000000..14b67773ba --- /dev/null +++ b/machine-learning/export/immich_model_exporter/exporters/onnx/models/openclip.py @@ -0,0 +1,153 @@ +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, + no_cache: bool = False, +) -> 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 not no_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 no_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 no_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"}}, + ) diff --git a/machine-learning/export/models/util.py b/machine-learning/export/immich_model_exporter/exporters/onnx/models/util.py similarity index 100% rename from machine-learning/export/models/util.py rename to machine-learning/export/immich_model_exporter/exporters/onnx/models/util.py diff --git a/machine-learning/export/immich_model_exporter/exporters/rknn.py b/machine-learning/export/immich_model_exporter/exporters/rknn.py new file mode 100644 index 0000000000..fb76fcc5ff --- /dev/null +++ b/machine-learning/export/immich_model_exporter/exporters/rknn.py @@ -0,0 +1,96 @@ +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, + no_cache: bool = False, +) -> None: + from rknn.api import RKNN + + input_path = model_dir / "model.onnx" + output_path = model_dir / "rknpu" / target_platform / "model.rknn" + if not no_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, + no_cache: bool = False, +) -> 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, + no_cache=no_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, + no_cache=no_cache, + ) + + +def export(model_dir: Path, no_cache: bool = False) -> None: + textual = model_dir / "textual" + visual = model_dir / "visual" + detection = model_dir / "detection" + recognition = model_dir / "recognition" + + if textual.is_dir(): + _export_platforms(textual, no_cache=no_cache) + + if visual.is_dir(): + _export_platforms(visual, no_cache=no_cache) + + if detection.is_dir(): + _export_platforms(detection, inputs=["input.1"], input_size_list=[[1, 3, 640, 640]], no_cache=no_cache) + + if recognition.is_dir(): + _export_platforms(recognition, inputs=["input.1"], input_size_list=[[1, 3, 112, 112]], no_cache=no_cache) diff --git a/machine-learning/export/immich_model_exporter/run.py b/machine-learning/export/immich_model_exporter/run.py new file mode 100644 index 0000000000..3554abb529 --- /dev/null +++ b/machine-learning/export/immich_model_exporter/run.py @@ -0,0 +1,88 @@ +import subprocess + +from exporters.constants import ModelSource + +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: + for model in models: + try: + print(f"Exporting model {model}") + subprocess.check_call(["python", "-m", "immich_model_exporter.export", model, source]) + 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) diff --git a/machine-learning/export/models/openclip.py b/machine-learning/export/models/openclip.py deleted file mode 100644 index 68a4b90353..0000000000 --- a/machine-learning/export/models/openclip.py +++ /dev/null @@ -1,114 +0,0 @@ -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"}}, - ) diff --git a/machine-learning/export/models/optimize.py b/machine-learning/export/models/optimize.py deleted file mode 100644 index 48b0c67634..0000000000 --- a/machine-learning/export/models/optimize.py +++ /dev/null @@ -1,49 +0,0 @@ -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) diff --git a/machine-learning/export/pyproject.toml b/machine-learning/export/pyproject.toml new file mode 100644 index 0000000000..cb2567c3b1 --- /dev/null +++ b/machine-learning/export/pyproject.toml @@ -0,0 +1,67 @@ +[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", +] + +[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.uv.sources] +torch = [{ index = "pytorch-cpu" }] +torchvision = [{ index = "pytorch-cpu" }] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[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'] diff --git a/machine-learning/export/run.py b/machine-learning/export/run.py deleted file mode 100644 index 3edb5644e3..0000000000 --- a/machine-learning/export/run.py +++ /dev/null @@ -1,113 +0,0 @@ -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) diff --git a/machine-learning/export/uv.lock b/machine-learning/export/uv.lock new file mode 100644 index 0000000000..0a4a8ebb68 --- /dev/null +++ b/machine-learning/export/uv.lock @@ -0,0 +1,1395 @@ +version = 1 +revision = 1 +requires-python = ">=3.10, <4.0" +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] + +[manifest] +overrides = [ + { name = "onnx", specifier = ">=1.16.0,<2" }, + { name = "onnxruntime", specifier = ">=1.18.2,<2" }, + { name = "torch", specifier = ">=2.4", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torchvision", specifier = ">=0.21", index = "https://download.pytorch.org/whl/cpu" }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419 }, + { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080 }, + { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886 }, + { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404 }, + { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, + { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, + { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, + { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, + { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, + { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, + { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, + { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 }, +] + +[[package]] +name = "fast-histogram" +version = "0.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/77/04a9b4b5caa6e6b3a2f633b15dec0996c1559fc26e9ba73bb3d1d844c874/fast_histogram-0.14.tar.gz", hash = "sha256:390973b98af22bda85c29dcf6f008ba0d626321e9bd3f5a9d7a43e5690ea69ea", size = 47469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/a3/acf5d7641585da06982027a11727b174c4f9311c13b422111c5f197c1a57/fast_histogram-0.14-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:15876672df4831177344dfd0afbf5fd532c78f7bfca8bfabcb0f3d558f672e99", size = 21597 }, + { url = "https://files.pythonhosted.org/packages/0c/2c/d4d96c78e72031f3171fb3a584b557d79d191e9bb4e93747f793c18f8623/fast_histogram-0.14-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:01f26dd20166040c50b5381f0a76635d81d5db9cfaaed7ec30103edf71e88c3f", size = 20634 }, + { url = "https://files.pythonhosted.org/packages/0f/f9/524b8a302862bdc7100a5e0662d3fa49500af20badcabaddeec474819b8d/fast_histogram-0.14-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b425d93e4bf1b0cdc223b8fe91ca68aa53c314b8ec374027b9a215a41aa85658", size = 55480 }, + { url = "https://files.pythonhosted.org/packages/50/3e/f0dba6333dbe5c5a338d1466939c8733256a5f6d7e10615b8f96a90277e5/fast_histogram-0.14-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f2f1d4b091fa065fc1991dd10f06812cfba7549622bf63f7888ac1c8c7ed9bb", size = 55426 }, + { url = "https://files.pythonhosted.org/packages/e8/6e/fdd53002da2c1c5f3694eb98f015728e842c2d26dd28fba618a04efadb4a/fast_histogram-0.14-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1a263da3d832e8faa10c7228b23028ac4a406d2dd7cebbe89b2d8a9a6d58a0c", size = 43564 }, + { url = "https://files.pythonhosted.org/packages/9a/bc/30658ca273e521b72faa8870dc2e5af0052d92d7e302c2ef50ab81f937cb/fast_histogram-0.14-cp39-abi3-win32.whl", hash = "sha256:b96db6ed1db9d1ce09800e88833cc8c5e9565d44748f7bf623c0694e6cce1e2d", size = 21300 }, + { url = "https://files.pythonhosted.org/packages/fa/d6/7bdb0ea7bc96fbd633c028927f51f84982e30b08120b98193535087cc34e/fast_histogram-0.14-cp39-abi3-win_amd64.whl", hash = "sha256:ff9b83b0d9d489e3a59ef3b18342db7cf75f76ae22c7d95ca143783c6cc307a6", size = 24116 }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "flatbuffers" +version = "25.2.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953 }, +] + +[[package]] +name = "fsspec" +version = "2025.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, +] + +[[package]] +name = "ftfy" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821 }, +] + +[[package]] +name = "huggingface-hub" +version = "0.29.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { 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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/0c/37d380846a2e5c9a3c6a73d26ffbcfdcad5fc3eacf42fdf7cff56f2af634/huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa", size = 468997 }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "immich-model-exporter" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "huggingface-hub" }, + { name = "multilingual-clip" }, + { name = "onnx" }, + { name = "onnxruntime" }, + { name = "open-clip-torch" }, + { name = "rknn-toolkit2" }, + { name = "tenacity" }, + { name = "transformers" }, + { name = "typer" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black" }, + { name = "mypy" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "huggingface-hub", specifier = ">=0.29.3" }, + { name = "multilingual-clip", specifier = ">=1.0.10" }, + { name = "onnx", specifier = ">=1.14.1" }, + { name = "onnxruntime", specifier = ">=1.16.0" }, + { name = "open-clip-torch", specifier = ">=2.31.0" }, + { name = "rknn-toolkit2", specifier = ">=2.3.0" }, + { name = "tenacity", specifier = ">=9.0.0" }, + { name = "transformers", specifier = ">=4.49.0" }, + { name = "typer", specifier = ">=0.15.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "black", specifier = ">=23.3.0" }, + { name = "mypy", specifier = ">=1.3.0" }, + { name = "ruff", specifier = ">=0.0.272" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, +] + +[[package]] +name = "multilingual-clip" +version = "1.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/18/a9aecb457c904696e9800d2ac538f364b23a3c7b8815326a45d9f3741a24/multilingual_clip-1.0.10.tar.gz", hash = "sha256:eea1ef03ce91735636ddcd4c887f6ea54a7e45f47d4a06deef1dbe2ce8dec19c", size = 17709 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/f7/575f65ab34993153e9bc88ea5e58d59475bd9f191d2db29729e01c4231f6/multilingual_clip-1.0.10-py3-none-any.whl", hash = "sha256:b9acf95b8309c85a0db5e9c88c5f1b400687e08d72408c460731ae31e71dc73a", size = 20403 }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433 }, + { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472 }, + { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424 }, + { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450 }, + { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765 }, + { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701 }, + { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 }, + { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 }, + { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 }, + { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 }, + { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 }, + { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 }, + { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, + { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, + { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, + { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, + { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, + { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468 }, + { url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411 }, + { url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889 }, + { url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746 }, + { url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620 }, + { url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659 }, + { url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905 }, + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554 }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127 }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994 }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005 }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297 }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567 }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812 }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913 }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901 }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868 }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109 }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613 }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172 }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643 }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803 }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754 }, +] + +[[package]] +name = "onnx" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/54/0e385c26bf230d223810a9c7d06628d954008a5e5e4b73ee26ef02327282/onnx-1.17.0.tar.gz", hash = "sha256:48ca1a91ff73c1d5e3ea2eef20ae5d0e709bb8a2355ed798ffc2169753013fd3", size = 12165120 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/29/57053ba7787788ac75efb095cfc1ae290436b6d3a26754693cd7ed1b4fac/onnx-1.17.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:38b5df0eb22012198cdcee527cc5f917f09cce1f88a69248aaca22bd78a7f023", size = 16645616 }, + { url = "https://files.pythonhosted.org/packages/75/0d/831807a18db2a5e8f7813848c59272b904a4ef3939fe4d1288cbce9ea735/onnx-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d545335cb49d4d8c47cc803d3a805deb7ad5d9094dc67657d66e568610a36d7d", size = 15908420 }, + { url = "https://files.pythonhosted.org/packages/dd/5b/c4f95dbe652d14aeba9afaceb177e9ffc48ac3c03048dd3f872f26f07e34/onnx-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3193a3672fc60f1a18c0f4c93ac81b761bc72fd8a6c2035fa79ff5969f07713e", size = 16046244 }, + { url = "https://files.pythonhosted.org/packages/08/a9/c1f218085043dccc6311460239e253fa6957cf12ee4b0a56b82014938d0b/onnx-1.17.0-cp310-cp310-win32.whl", hash = "sha256:0141c2ce806c474b667b7e4499164227ef594584da432fd5613ec17c1855e311", size = 14423516 }, + { url = "https://files.pythonhosted.org/packages/0e/d3/d26ebf590a65686dde6b27fef32493026c5be9e42083340d947395f93405/onnx-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:dfd777d95c158437fda6b34758f0877d15b89cbe9ff45affbedc519b35345cf9", size = 14528496 }, + { url = "https://files.pythonhosted.org/packages/e5/a9/8d1b1d53aec70df53e0f57e9f9fcf47004276539e29230c3d5f1f50719ba/onnx-1.17.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:d6fc3a03fc0129b8b6ac03f03bc894431ffd77c7d79ec023d0afd667b4d35869", size = 16647991 }, + { url = "https://files.pythonhosted.org/packages/7b/e3/cc80110e5996ca61878f7b4c73c7a286cd88918ff35eacb60dc75ab11ef5/onnx-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01a4b63d4e1d8ec3e2f069e7b798b2955810aa434f7361f01bc8ca08d69cce4", size = 15908949 }, + { url = "https://files.pythonhosted.org/packages/b1/2f/91092557ed478e323a2b4471e2081fdf88d1dd52ae988ceaf7db4e4506ff/onnx-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a183c6178be001bf398260e5ac2c927dc43e7746e8638d6c05c20e321f8c949", size = 16048190 }, + { url = "https://files.pythonhosted.org/packages/ac/59/9ea23fc22d0bb853133f363e6248e31bcbc6c1c90543a3938c00412ac02a/onnx-1.17.0-cp311-cp311-win32.whl", hash = "sha256:081ec43a8b950171767d99075b6b92553901fa429d4bc5eb3ad66b36ef5dbe3a", size = 14424299 }, + { url = "https://files.pythonhosted.org/packages/51/a5/19b0dfcb567b62e7adf1a21b08b23224f0c2d13842aee4d0abc6f07f9cf5/onnx-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:95c03e38671785036bb704c30cd2e150825f6ab4763df3a4f1d249da48525957", size = 14529142 }, + { url = "https://files.pythonhosted.org/packages/b4/dd/c416a11a28847fafb0db1bf43381979a0f522eb9107b831058fde012dd56/onnx-1.17.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:0e906e6a83437de05f8139ea7eaf366bf287f44ae5cc44b2850a30e296421f2f", size = 16651271 }, + { url = "https://files.pythonhosted.org/packages/f0/6c/f040652277f514ecd81b7251841f96caa5538365af7df07f86c6018cda2b/onnx-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d955ba2939878a520a97614bcf2e79c1df71b29203e8ced478fa78c9a9c63c2", size = 15907522 }, + { url = "https://files.pythonhosted.org/packages/3d/7c/67f4952d1b56b3f74a154b97d0dd0630d525923b354db117d04823b8b49b/onnx-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f3fb5cc4e2898ac5312a7dc03a65133dd2abf9a5e520e69afb880a7251ec97a", size = 16046307 }, + { url = "https://files.pythonhosted.org/packages/ae/20/6da11042d2ab870dfb4ce4a6b52354d7651b6b4112038b6d2229ab9904c4/onnx-1.17.0-cp312-cp312-win32.whl", hash = "sha256:317870fca3349d19325a4b7d1b5628f6de3811e9710b1e3665c68b073d0e68d7", size = 14424235 }, + { url = "https://files.pythonhosted.org/packages/35/55/c4d11bee1fdb0c4bd84b4e3562ff811a19b63266816870ae1f95567aa6e1/onnx-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:659b8232d627a5460d74fd3c96947ae83db6d03f035ac633e20cd69cfa029227", size = 14530453 }, +] + +[[package]] +name = "onnxoptimizer" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "onnx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/c2/8fcbecf89c52ca3beaccbed4aedb05eb9aa2bf232e18987942b3e1d3f227/onnxoptimizer-0.3.8.tar.gz", hash = "sha256:158bf9fda2e33bfe8ba1484f295cf25447921822f840de90831f7007af1295d3", size = 18462997 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/31/f5e680b1236716979d30a03599b6b95b0d18cf8fa9eab32f4d0f7f998839/onnxoptimizer-0.3.8-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:6e40530dcc08b5d504c150da90be68f7e438aeb7008dd0d79cd20058944d4087", size = 551097 }, + { url = "https://files.pythonhosted.org/packages/43/7b/bf49c6a7d5daea9173e2f5fef27cea7929adbc3fd12fd855b7fd453f3553/onnxoptimizer-0.3.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecee31591b9e11075bfa9b46778fb7ae6ae9861f8b2187b7455cbe1529638132", size = 647156 }, + { url = "https://files.pythonhosted.org/packages/be/5b/76595fa404600b599cf33d147593c8a318d5ba027d0c41cae265b6e8b2b4/onnxoptimizer-0.3.8-cp310-cp310-win_amd64.whl", hash = "sha256:e8469416d81973aea588fe0b85365a413e79cb7b155e3ca5be538b6492669944", size = 358290 }, + { url = "https://files.pythonhosted.org/packages/0d/15/4dc6e5e146c78e56ded278082bdad3b31b5f84dd4872641059db81636872/onnxoptimizer-0.3.8-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:12ab4825d809e96c3978230d6d7696ddef87bb91837f5ec11f4cb96b3fe72a09", size = 551149 }, + { url = "https://files.pythonhosted.org/packages/27/9c/13a04a34985592edeb0e8ef579abd04f97cde6edab08223610b4f71320b1/onnxoptimizer-0.3.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7241b9f1069e02621ce0762f6a013d1af84eb12542b2c4159817965c8b09b9", size = 647183 }, + { url = "https://files.pythonhosted.org/packages/df/f9/417224261eb122fa8279adb99151b880ac4e99416b21d474206ce1d2d31e/onnxoptimizer-0.3.8-cp311-cp311-win_amd64.whl", hash = "sha256:62029b1e83df4706079a135f23626b460cdfb47b33df0236e6bda5148702b007", size = 358294 }, +] + +[[package]] +name = "onnxruntime" +version = "1.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b5/433e46baf8f31a84684f9d3446d8683473706e2810b6171e19beed88ecb9/onnxruntime-1.21.0-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:95513c9302bc8dd013d84148dcf3168e782a80cdbf1654eddc948a23147ccd3d", size = 33639595 }, + { url = "https://files.pythonhosted.org/packages/23/78/1ec7358f9c9de82299cb99a1a48bdb871b4180533cfe5900e2ede102668e/onnxruntime-1.21.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:635d4ab13ae0f150dd4c6ff8206fd58f1c6600636ecc796f6f0c42e4c918585b", size = 14159036 }, + { url = "https://files.pythonhosted.org/packages/eb/66/fcd3e1201f546c736b0050cb2e889296596ff7862f36bd17027fbef5f24d/onnxruntime-1.21.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d06bfa0dd5512bd164f25a2bf594b2e7c9eabda6fc064b684924f3e81bdab1b", size = 16000047 }, + { url = "https://files.pythonhosted.org/packages/29/eb/16abd29cdff9cb3237ba13adfafad20048c8f5a4a50b7e4689dd556c58d6/onnxruntime-1.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:b0fc22d219791e0284ee1d9c26724b8ee3fbdea28128ef25d9507ad3b9621f23", size = 11758587 }, + { url = "https://files.pythonhosted.org/packages/df/34/fd780c62b3ec9268224ada4205a5256618553b8cc26d7205d3cf8aafde47/onnxruntime-1.21.0-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:8e16f8a79df03919810852fb46ffcc916dc87a9e9c6540a58f20c914c575678c", size = 33644022 }, + { url = "https://files.pythonhosted.org/packages/7b/df/622594b43d1a8644ac4d947f52e34a0e813b3d76a62af34667e343c34e98/onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9156cf6f8ee133d07a751e6518cf6f84ed37fbf8243156bd4a2c4ee6e073c8", size = 14159570 }, + { url = "https://files.pythonhosted.org/packages/f9/49/1e916e8d1d957a1432c1662ef2e94f3e4afab31f6f1888fb80d4da374a5d/onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a5d09815a9e209fa0cb20c2985b34ab4daeba7aea94d0f96b8751eb10403201", size = 16001965 }, + { url = "https://files.pythonhosted.org/packages/09/05/15ec0933f8543f85743571da9b3bf4397f71792c9d375f01f61c6019f130/onnxruntime-1.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:1d970dff1e2fa4d9c53f2787b3b7d0005596866e6a31997b41169017d1362dd0", size = 11759373 }, + { url = "https://files.pythonhosted.org/packages/ff/21/593c9bc56002a6d1ea7c2236f4a648e081ec37c8d51db2383a9e83a63325/onnxruntime-1.21.0-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:893d67c68ca9e7a58202fa8d96061ed86a5815b0925b5a97aef27b8ba246a20b", size = 33658780 }, + { url = "https://files.pythonhosted.org/packages/4a/b4/33ec675a8ac150478091262824413e5d4acc359e029af87f9152e7c1c092/onnxruntime-1.21.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37b7445c920a96271a8dfa16855e258dc5599235b41c7bbde0d262d55bcc105f", size = 14159975 }, + { url = "https://files.pythonhosted.org/packages/8b/08/eead6895ed83b56711ca6c0d31d82f109401b9937558b425509e497d6fb4/onnxruntime-1.21.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a04aafb802c1e5573ba4552f8babcb5021b041eb4cfa802c9b7644ca3510eca", size = 16019285 }, + { url = "https://files.pythonhosted.org/packages/77/39/e83d56e3c215713b5263cb4d4f0c69e3964bba11634233d8ae04fc7e6bf3/onnxruntime-1.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f801318476cd7003d636a5b392f7a37c08b6c8d2f829773f3c3887029e03f32", size = 11760975 }, + { url = "https://files.pythonhosted.org/packages/f2/25/93f65617b06c741a58eeac9e373c99df443b02a774f4cb6511889757c0da/onnxruntime-1.21.0-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:85718cbde1c2912d3a03e3b3dc181b1480258a229c32378408cace7c450f7f23", size = 33659581 }, + { url = "https://files.pythonhosted.org/packages/f9/03/6b6829ee8344490ab5197f39a6824499ed097d1fc8c85b1f91c0e6767819/onnxruntime-1.21.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94dff3a61538f3b7b0ea9a06bc99e1410e90509c76e3a746f039e417802a12ae", size = 14160534 }, + { url = "https://files.pythonhosted.org/packages/a6/81/e280ddf05f83ad5e0d066ef08e31515b17bd50bb52ef2ea713d9e455e67a/onnxruntime-1.21.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1e704b0eda5f2bbbe84182437315eaec89a450b08854b5a7762c85d04a28a0a", size = 16018947 }, + { url = "https://files.pythonhosted.org/packages/d3/ea/011dfc2536e46e2ea984d2c0256dc585ebb1352366dffdd98764f1f44ee4/onnxruntime-1.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:19b630c6a8956ef97fb7c94948b17691167aa1aaf07b5f214fa66c3e4136c108", size = 11760731 }, + { url = "https://files.pythonhosted.org/packages/47/6b/a00f31322e91c610c7825377ef0cad884483c30d1370b896d57e7032e912/onnxruntime-1.21.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3995c4a2d81719623c58697b9510f8de9fa42a1da6b4474052797b0d712324fe", size = 14172215 }, + { url = "https://files.pythonhosted.org/packages/58/4b/98214f13ac1cd675dfc2713ba47b5722f55ce4fba526d2b2826f2682a42e/onnxruntime-1.21.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36b18b8f39c0f84e783902112a0dd3c102466897f96d73bb83f6a6bff283a423", size = 15990612 }, +] + +[[package]] +name = "open-clip-torch" +version = "2.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ftfy" }, + { name = "huggingface-hub" }, + { name = "regex" }, + { name = "safetensors" }, + { name = "timm" }, + { name = "torch", version = "2.6.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.6.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "torchvision", version = "0.21.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.21.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/2d/df12ee8417c918fde4c8c6af470ae2c2d2dc75971dc1e3cc7f108a3cd92a/open_clip_torch-2.31.0.tar.gz", hash = "sha256:a3998b5ac13a1d11b003e6147f2dea834c7a48b3fd7d902fc17a0fff7638e6ee", size = 1486178 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/91/8b1da3a397948edb8082f2838a24de7dda807adae6cbfcd56db475b9df16/open_clip_torch-2.31.0-py3-none-any.whl", hash = "sha256:92dd8c2d6d994e061de46465e711f87b682858842eb9926130e401014d33af0d", size = 1523658 }, +] + +[[package]] +name = "opencv-python" +version = "4.11.0.86" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322 }, + { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197 }, + { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439 }, + { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597 }, + { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "pillow" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/1c/2dcea34ac3d7bc96a1fd1bd0a6e06a57c67167fec2cff8d95d88229a8817/pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8", size = 3229983 }, + { url = "https://files.pythonhosted.org/packages/14/ca/6bec3df25e4c88432681de94a3531cc738bd85dea6c7aa6ab6f81ad8bd11/pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192", size = 3101831 }, + { url = "https://files.pythonhosted.org/packages/d4/2c/668e18e5521e46eb9667b09e501d8e07049eb5bfe39d56be0724a43117e6/pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2", size = 4314074 }, + { url = "https://files.pythonhosted.org/packages/02/80/79f99b714f0fc25f6a8499ecfd1f810df12aec170ea1e32a4f75746051ce/pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26", size = 4394933 }, + { url = "https://files.pythonhosted.org/packages/81/aa/8d4ad25dc11fd10a2001d5b8a80fdc0e564ac33b293bdfe04ed387e0fd95/pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07", size = 4353349 }, + { url = "https://files.pythonhosted.org/packages/84/7a/cd0c3eaf4a28cb2a74bdd19129f7726277a7f30c4f8424cd27a62987d864/pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482", size = 4476532 }, + { url = "https://files.pythonhosted.org/packages/8f/8b/a907fdd3ae8f01c7670dfb1499c53c28e217c338b47a813af8d815e7ce97/pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e", size = 4279789 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/9f139d9e8cccd661c3efbf6898967a9a337eb2e9be2b454ba0a09533100d/pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269", size = 4413131 }, + { url = "https://files.pythonhosted.org/packages/a8/68/0d8d461f42a3f37432203c8e6df94da10ac8081b6d35af1c203bf3111088/pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49", size = 2291213 }, + { url = "https://files.pythonhosted.org/packages/14/81/d0dff759a74ba87715509af9f6cb21fa21d93b02b3316ed43bda83664db9/pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a", size = 2625725 }, + { url = "https://files.pythonhosted.org/packages/ce/1f/8d50c096a1d58ef0584ddc37e6f602828515219e9d2428e14ce50f5ecad1/pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65", size = 2375213 }, + { url = "https://files.pythonhosted.org/packages/dd/d6/2000bfd8d5414fb70cbbe52c8332f2283ff30ed66a9cde42716c8ecbe22c/pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457", size = 3229968 }, + { url = "https://files.pythonhosted.org/packages/d9/45/3fe487010dd9ce0a06adf9b8ff4f273cc0a44536e234b0fad3532a42c15b/pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35", size = 3101806 }, + { url = "https://files.pythonhosted.org/packages/e3/72/776b3629c47d9d5f1c160113158a7a7ad177688d3a1159cd3b62ded5a33a/pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2", size = 4322283 }, + { url = "https://files.pythonhosted.org/packages/e4/c2/e25199e7e4e71d64eeb869f5b72c7ddec70e0a87926398785ab944d92375/pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070", size = 4402945 }, + { url = "https://files.pythonhosted.org/packages/c1/ed/51d6136c9d5911f78632b1b86c45241c712c5a80ed7fa7f9120a5dff1eba/pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6", size = 4361228 }, + { url = "https://files.pythonhosted.org/packages/48/a4/fbfe9d5581d7b111b28f1d8c2762dee92e9821bb209af9fa83c940e507a0/pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1", size = 4484021 }, + { url = "https://files.pythonhosted.org/packages/39/db/0b3c1a5018117f3c1d4df671fb8e47d08937f27519e8614bbe86153b65a5/pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2", size = 4287449 }, + { url = "https://files.pythonhosted.org/packages/d9/58/bc128da7fea8c89fc85e09f773c4901e95b5936000e6f303222490c052f3/pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96", size = 4419972 }, + { url = "https://files.pythonhosted.org/packages/5f/bb/58f34379bde9fe197f51841c5bbe8830c28bbb6d3801f16a83b8f2ad37df/pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f", size = 2291201 }, + { url = "https://files.pythonhosted.org/packages/3a/c6/fce9255272bcf0c39e15abd2f8fd8429a954cf344469eaceb9d0d1366913/pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761", size = 2625686 }, + { url = "https://files.pythonhosted.org/packages/c8/52/8ba066d569d932365509054859f74f2a9abee273edcef5cd75e4bc3e831e/pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71", size = 2375194 }, + { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, + { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, + { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, + { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, + { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, + { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, + { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, + { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, + { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, + { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, + { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, + { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, + { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, + { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, + { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, + { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, + { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, + { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, + { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, + { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, + { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, + { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, + { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, + { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, + { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, + { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, + { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, + { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, + { url = "https://files.pythonhosted.org/packages/fa/c5/389961578fb677b8b3244fcd934f720ed25a148b9a5cc81c91bdf59d8588/pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90", size = 3198345 }, + { url = "https://files.pythonhosted.org/packages/c4/fa/803c0e50ffee74d4b965229e816af55276eac1d5806712de86f9371858fd/pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb", size = 3072938 }, + { url = "https://files.pythonhosted.org/packages/dc/67/2a3a5f8012b5d8c63fe53958ba906c1b1d0482ebed5618057ef4d22f8076/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442", size = 3400049 }, + { url = "https://files.pythonhosted.org/packages/e5/a0/514f0d317446c98c478d1872497eb92e7cde67003fed74f696441e647446/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83", size = 3422431 }, + { url = "https://files.pythonhosted.org/packages/cd/00/20f40a935514037b7d3f87adfc87d2c538430ea625b63b3af8c3f5578e72/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f", size = 3446208 }, + { url = "https://files.pythonhosted.org/packages/28/3c/7de681727963043e093c72e6c3348411b0185eab3263100d4490234ba2f6/pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73", size = 3509746 }, + { url = "https://files.pythonhosted.org/packages/41/67/936f9814bdd74b2dfd4822f1f7725ab5d8ff4103919a1664eb4874c58b2f/pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0", size = 2626353 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "protobuf" +version = "3.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/5b/e3d951e34f8356e5feecacd12a8e3b258a1da6d9a03ad1770f28925f29bc/protobuf-3.20.3.tar.gz", hash = "sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2", size = 216768 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/55/b80e8567ec327c060fa39b242392e25690c8899c489ecd7bb65b46b7bb55/protobuf-3.20.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99", size = 918427 }, + { url = "https://files.pythonhosted.org/packages/31/be/80a9c6f16dfa4d41be3edbe655349778ae30882407fa8275eb46b4d34854/protobuf-3.20.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e", size = 1051042 }, + { url = "https://files.pythonhosted.org/packages/db/96/948d3fcc1fa816e7ae1d27af59b9d8c5c5e582f3994fd14394f31da95b99/protobuf-3.20.3-cp310-cp310-win32.whl", hash = "sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c", size = 780167 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/fc6feb366b0a9f28e0a2de3b062667c521cd9517d4ff55077b8f351ba2f3/protobuf-3.20.3-cp310-cp310-win_amd64.whl", hash = "sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7", size = 904029 }, + { url = "https://files.pythonhosted.org/packages/8d/14/619e24a4c70df2901e1f4dbc50a6291eb63a759172558df326347dce1f0d/protobuf-3.20.3-py2.py3-none-any.whl", hash = "sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db", size = 162128 }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674 }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511 }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149 }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707 }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702 }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976 }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397 }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726 }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098 }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325 }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277 }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197 }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714 }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042 }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669 }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684 }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589 }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121 }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275 }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257 }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727 }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667 }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963 }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700 }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592 }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929 }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213 }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734 }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052 }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { 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-toolkit2" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fast-histogram" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "onnxoptimizer" }, + { name = "onnxruntime" }, + { name = "opencv-python" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "ruamel-yaml" }, + { name = "scipy" }, + { name = "torch", version = "2.6.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.6.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "tqdm" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/e7/68ac7e48959965d97bae31cc8f2058b5cecaa1f24e6ae0cd899da8de234d/rknn_toolkit2-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1166109316ac885d3792eda8704980eb4bb19edc45f1c8cfebd3a3f4fc0036a", size = 36437128 }, + { url = "https://files.pythonhosted.org/packages/89/35/95b8ba25901e6aafd25d1b0f03144bbb279e69284f9e32bb59f0cd60da9b/rknn_toolkit2-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8a574cded9f49093b0ba66ce90c88544dde01a46f1467f7ee9145fe16cad2", size = 38184916 }, + { url = "https://files.pythonhosted.org/packages/e5/26/54362213849e3b0fa9d29f9af6bdeaa1c5b49b44e8944e6f9c27c68c4971/rknn_toolkit2-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdb72d65ad40c88cf348cf0eebd464755c851e2762ffd87d91115f38739971fa", size = 36540495 }, + { url = "https://files.pythonhosted.org/packages/c6/58/928bc857180efe4a3b0efbab1fd163e0fe305909ea93bec3c3973ced5c9b/rknn_toolkit2-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2ac95958862fac785f8db4c40ef29b143c400a0c7e412b4384d43368c7396dd", size = 38267509 }, + { url = "https://files.pythonhosted.org/packages/37/09/ef4c7a8eff43f6b20f556bc41b8108349871db198be8651871ff7195d35c/rknn_toolkit2-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d5c064a9a33dbaf3727e948b206491b88356fca3ecdcb15cee5696a37f58cdc", size = 35273559 }, + { url = "https://files.pythonhosted.org/packages/46/48/6b34dfa8a184c0bf9ab6a80e9ec71916deebaab17f3d83245850484cefe0/rknn_toolkit2-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb19db7d26dd3cb6343633391ef3d71bada7525666fde607747cca2f5b9b280", size = 37585392 }, +] + +[[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]] +name = "ruff" +version = "0.11.0" +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 } +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/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/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/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/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/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/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/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/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/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/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/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/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/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/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, + { 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/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, +] + +[[package]] +name = "safetensors" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917 }, + { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419 }, + { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493 }, + { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400 }, + { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891 }, + { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694 }, + { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642 }, + { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241 }, + { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001 }, + { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013 }, + { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687 }, + { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147 }, + { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677 }, + { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878 }, +] + +[[package]] +name = "scipy" +version = "1.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/df/ef233fff6838fe6f7840d69b5ef9f20d2b5c912a8727b21ebf876cb15d54/scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9", size = 38692502 }, + { url = "https://files.pythonhosted.org/packages/5c/20/acdd4efb8a68b842968f7bc5611b1aeb819794508771ad104de418701422/scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5", size = 30085508 }, + { url = "https://files.pythonhosted.org/packages/42/55/39cf96ca7126f1e78ee72a6344ebdc6702fc47d037319ad93221063e6cf4/scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e", size = 22359166 }, + { url = "https://files.pythonhosted.org/packages/51/48/708d26a4ab8a1441536bf2dfcad1df0ca14a69f010fba3ccbdfc02df7185/scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9", size = 25112047 }, + { url = "https://files.pythonhosted.org/packages/dd/65/f9c5755b995ad892020381b8ae11f16d18616208e388621dfacc11df6de6/scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3", size = 35536214 }, + { url = "https://files.pythonhosted.org/packages/de/3c/c96d904b9892beec978562f64d8cc43f9cca0842e65bd3cd1b7f7389b0ba/scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d", size = 37646981 }, + { url = "https://files.pythonhosted.org/packages/3d/74/c2d8a24d18acdeae69ed02e132b9bc1bb67b7bee90feee1afe05a68f9d67/scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58", size = 37230048 }, + { url = "https://files.pythonhosted.org/packages/42/19/0aa4ce80eca82d487987eff0bc754f014dec10d20de2f66754fa4ea70204/scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa", size = 40010322 }, + { url = "https://files.pythonhosted.org/packages/d0/d2/f0683b7e992be44d1475cc144d1f1eeae63c73a14f862974b4db64af635e/scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65", size = 41233385 }, + { url = "https://files.pythonhosted.org/packages/40/1f/bf0a5f338bda7c35c08b4ed0df797e7bafe8a78a97275e9f439aceb46193/scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4", size = 38703651 }, + { url = "https://files.pythonhosted.org/packages/de/54/db126aad3874601048c2c20ae3d8a433dbfd7ba8381551e6f62606d9bd8e/scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1", size = 30102038 }, + { url = "https://files.pythonhosted.org/packages/61/d8/84da3fffefb6c7d5a16968fe5b9f24c98606b165bb801bb0b8bc3985200f/scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971", size = 22375518 }, + { url = "https://files.pythonhosted.org/packages/44/78/25535a6e63d3b9c4c90147371aedb5d04c72f3aee3a34451f2dc27c0c07f/scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655", size = 25142523 }, + { url = "https://files.pythonhosted.org/packages/e0/22/4b4a26fe1cd9ed0bc2b2cb87b17d57e32ab72c346949eaf9288001f8aa8e/scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e", size = 35491547 }, + { url = "https://files.pythonhosted.org/packages/32/ea/564bacc26b676c06a00266a3f25fdfe91a9d9a2532ccea7ce6dd394541bc/scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0", size = 37634077 }, + { url = "https://files.pythonhosted.org/packages/43/c2/bfd4e60668897a303b0ffb7191e965a5da4056f0d98acfb6ba529678f0fb/scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40", size = 37231657 }, + { url = "https://files.pythonhosted.org/packages/4a/75/5f13050bf4f84c931bcab4f4e83c212a36876c3c2244475db34e4b5fe1a6/scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462", size = 40035857 }, + { url = "https://files.pythonhosted.org/packages/b9/8b/7ec1832b09dbc88f3db411f8cdd47db04505c4b72c99b11c920a8f0479c3/scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737", size = 41217654 }, + { url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184 }, + { url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558 }, + { url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211 }, + { url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260 }, + { url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095 }, + { url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371 }, + { url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390 }, + { url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276 }, + { url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317 }, + { url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587 }, + { url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266 }, + { url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768 }, + { url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719 }, + { url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195 }, + { url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404 }, + { url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011 }, + { url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406 }, + { url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286 }, + { url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634 }, + { url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179 }, + { url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412 }, + { url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867 }, + { url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009 }, + { url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159 }, + { url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566 }, + { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705 }, +] + +[[package]] +name = "setuptools" +version = "76.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/32/d2/7b171caf085ba0d40d8391f54e1c75a1cda9255f542becf84575cfd8a732/setuptools-76.0.0.tar.gz", hash = "sha256:43b4ee60e10b0d0ee98ad11918e114c70701bc6051662a9a675a0496c1a158f4", size = 1349387 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/66/d2d7e6ad554f3a7c7297c3f8ef6e22643ad3d35ef5c63bf488bc89f32f31/setuptools-76.0.0-py3-none-any.whl", hash = "sha256:199466a166ff664970d0ee145839f5582cb9bca7a0a3a2e795b6a9cb2308e9c6", size = 1236106 }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "sympy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 }, +] + +[[package]] +name = "tenacity" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169 }, +] + +[[package]] +name = "timm" +version = "1.0.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.6.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.6.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, + { name = "torchvision", version = "0.21.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torchvision", version = "0.21.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/0c/66b0f9b4a4cb9ffdac7b52b17b37c7d3c4f75623b469e388b0c6d89b4e88/timm-1.0.15.tar.gz", hash = "sha256:756a3bc30c96565f056e608a9b559daed904617eaadb6be536f96874879b1055", size = 2230258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/d0/179abca8b984b3deefd996f362b612c39da73b60f685921e6cd58b6125b4/timm-1.0.15-py3-none-any.whl", hash = "sha256:5a3dc460c24e322ecc7fd1f3e3eb112423ddee320cb059cc1956fbc9731748ef", size = 2361373 }, +] + +[[package]] +name = "tokenizers" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/41/c2be10975ca37f6ec40d7abd7e98a5213bb04f284b869c1a24e6504fd94d/tokenizers-0.21.0.tar.gz", hash = "sha256:ee0894bf311b75b0c03079f33859ae4b2334d675d4e93f5a4132e1eae2834fe4", size = 343021 } +wheels = [ + { 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/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/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/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/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/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/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/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/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/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/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/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/15/b0/dc4572ca61555fc482ebc933f26cb407c6aceb3dc19c301c68184f8cad03/tokenizers-0.21.0-cp39-abi3-win32.whl", hash = "sha256:eb1702c2f27d25d9dd5b389cc1f2f51813e99f8ca30d9e25348db6585a97e24a", size = 2202266 }, + { url = "https://files.pythonhosted.org/packages/44/69/d21eb253fa91622da25585d362a874fa4710be600f0ea9446d8d0217cec1/tokenizers-0.21.0-cp39-abi3-win_amd64.whl", hash = "sha256:87841da5a25a3a5f70c102de371db120f41873b854ba65e52bccd57df5a3780c", size = 2389192 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "torch" +version = "2.6.0" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "sys_platform == 'darwin'" }, + { name = "fsspec", marker = "sys_platform == 'darwin'" }, + { name = "jinja2", marker = "sys_platform == 'darwin'" }, + { name = "networkx", marker = "sys_platform == 'darwin'" }, + { name = "setuptools", marker = "python_full_version >= '3.12' and sys_platform == 'darwin'" }, + { name = "sympy", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:09e06f9949e1a0518c5b09fe95295bc9661f219d9ecb6f9893e5123e10696628" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:94fc63b3b4bedd327af588696559f68c264440e2503cc9e6954019473d74ae21" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2" }, +] + +[[package]] +name = "torch" +version = "2.6.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +dependencies = [ + { name = "filelock", marker = "sys_platform != 'darwin'" }, + { name = "fsspec", marker = "sys_platform != 'darwin'" }, + { name = "jinja2", marker = "sys_platform != 'darwin'" }, + { name = "networkx", marker = "sys_platform != 'darwin'" }, + { name = "setuptools", marker = "python_full_version >= '3.12' and sys_platform != 'darwin'" }, + { name = "sympy", marker = "sys_platform != 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:35a9e78b7e4096968b54c1a198687b981569c50ae93e661aa430f9fd208da102" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:90832f4d118c566b8652a2196ac695fc1f14cf420db27b5a1b41c7eaaf2141e9" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:6e22f0b13db8d53e55bcb3b46c9dd4b6676d1c44051b56753e745cec3075b333" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:5b6ae523bfb67088a17ca7734d131548a2e60346c622621e4248ed09dd0790cc" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d3dab9fb0294f268aec28e8aaba834e9d006b90a50db5bc2fe2191a9d48c6084" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:24c9d3d13b9ea769dd7bd5c11cfa1fc463fd7391397156565484565ca685d908" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:59e78aa0c690f70734e42670036d6b541930b8eabbaa18d94e090abf14cc4d91" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:318290e8924353c61b125cdc8768d15208704e279e7757c113b9620740deca98" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:4027d982eb2781c93825ab9527f17fbbb12dbabf422298e4b954be60016f87d8" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp313-cp313-linux_x86_64.whl", hash = "sha256:e70ee2e37ad27a90201d101a41c2e10df7cf15a9ebd17c084f54cf2518c57bdf" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b5e7e8d561b263b5ad8049736281cd12c78e51e7bc1a913fd4098fd0e0b96347" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:b436a6c62d086dc5b32f5721b59f0ca8ad3bf9de09ee9b5b83dbf1e7a7e22c60" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp313-cp313t-linux_x86_64.whl", hash = "sha256:fb34d6cc4e6e20e66d74852c3d84e0301dc5e1a7c822076ef288886f978390f0" }, + { url = "https://download.pytorch.org/whl/cpu/torch-2.6.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:7cac05af909ee1c5c2915e8f3efaa1ea015e7e414be0ff53071402b9e4f3c7df" }, +] + +[[package]] +name = "torchvision" +version = "0.21.0" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", +] +dependencies = [ + { name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "pillow", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, + { name = "torch", version = "2.6.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.6.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp310-cp310-linux_aarch64.whl", hash = "sha256:54815e0a56dde95cc6ec952577f67e0dc151eadd928e8d9f6a7f821d69a4a734" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:044ea420b8c6c3162a234cada8e2025b9076fa82504758cd11ec5d0f8cd9fa37" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp311-cp311-linux_aarch64.whl", hash = "sha256:54454923a50104c66a9ab6bd8b73a11c2fc218c964b1006d5d1fe5b442c3dcb6" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110d115333524d60e9e474d53c7d20f096dbd8a080232f88dddb90566f90064c" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp312-cp312-linux_aarch64.whl", hash = "sha256:5083a5b1fec2351bf5ea9900a741d54086db75baec4b1d21e39451e00977f1b1" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97a5814a93c793aaf0179cfc7f916024f4b63218929aee977b645633d074a49f" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp313-cp313-linux_aarch64.whl", hash = "sha256:5045a3a5f21ec3eea6962fa5f2fa2d4283f854caec25ada493fcf4aab2925467" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:659b76c86757cb2ee4ca2db245e0740cfc3081fef46f0f1064d11adb4a8cee31" }, +] + +[[package]] +name = "torchvision" +version = "0.21.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.11.*' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", +] +dependencies = [ + { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, + { name = "torch", version = "2.6.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, +] +wheels = [ + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-linux_x86_64.whl", hash = "sha256:4ed0a1be50676a7c589ba83b62c9dc0267a87e852b8cd9b7d6db27ab36c6d552" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:554ca0f5948ac89911299f8bfb6f23936d867387ea213ab235adc2814b510d0c" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp311-cp311-linux_x86_64.whl", hash = "sha256:d67081026aad9642c46d3b14035f8ae69117468c09a07d628f3eafc7ae74841f" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:852b96738a68592223f01a04e4bcc1b3906bef7eee41c99f27f3be5706046862" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp312-cp312-linux_x86_64.whl", hash = "sha256:d6874431e678ba107b60a83f255c33f3755f06bad587b1b919aa514ec325dcd8" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:667f3d983240f41eaff5a3f78bdcbc144473978a37cd15a4db6dad92b1e8b6f0" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp313-cp313-linux_x86_64.whl", hash = "sha256:a76478c0f547e032116282d61a5a7d943142cf040f6c7d97941d7e96813c4c14" }, + { url = "https://download.pytorch.org/whl/cpu/torchvision-0.21.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:883f8668b923781f1152a20d75e75ad94a4f1016328d86a7b889006a9156fb14" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +] + +[[package]] +name = "transformers" +version = "4.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/50/46573150944f46df8ec968eda854023165a84470b42f69f67c7d475dabc5/transformers-4.49.0.tar.gz", hash = "sha256:7e40e640b5b8dc3f48743f5f5adbdce3660c82baafbd3afdfc04143cdbd2089e", size = 8610952 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/37/1f29af63e9c30156a3ed6ebc2754077016577c094f31de7b2631e5d379eb/transformers-4.49.0-py3-none-any.whl", hash = "sha256:6b4fded1c5fee04d384b1014495b4235a2b53c87503d7d592423c06128cbbe03", size = 9970275 }, +] + +[[package]] +name = "typer" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/6f/3991f0f1c7fcb2df31aef28e0594d8d54b05393a0e4e34c65e475c2a5d41/typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5", size = 100711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/fc/5b29fea8cee020515ca82cc68e3b8e1e34bb19a3535ad854cac9257b414c/typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc", size = 45061 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index b962b76dc8..140f727de3 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -51,6 +51,7 @@ cpu = ["onnxruntime>=1.15.0,<2"] cuda = ["onnxruntime-gpu>=1.17.0,<2"] openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"] armnn = ["onnxruntime>=1.15.0,<2"] +rknn = ["onnxruntime>=1.15.0,<2", "rknn-toolkit-lite2>=2.3.0,<3"] [tool.uv] compile-bytecode = true diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock index 55cf8ed555..32ac09c7c6 100644 --- a/machine-learning/uv.lock +++ b/machine-learning/uv.lock @@ -1109,6 +1109,10 @@ cuda = [ openvino = [ { name = "onnxruntime-openvino" }, ] +rknn = [ + { name = "onnxruntime" }, + { name = "rknn-toolkit-lite2" }, +] [package.dev-dependencies] dev = [ @@ -1162,6 +1166,7 @@ requires-dist = [ { name = "insightface", specifier = ">=0.7.3,<1.0" }, { name = "onnxruntime", marker = "extra == 'armnn'", 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-openvino", marker = "extra == 'openvino'", specifier = ">=1.17.1,<1.19.0" }, { name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" }, @@ -1171,10 +1176,11 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, { name = "python-multipart", specifier = ">=0.0.6,<1.0" }, { 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 = "uvicorn", extras = ["standard"], specifier = ">=0.22.0,<1.0" }, ] -provides-extras = ["cpu", "cuda", "openvino", "armnn"] +provides-extras = ["cpu", "cuda", "openvino", "armnn", "rknn"] [package.metadata.requires-dev] dev = [ @@ -2131,6 +2137,77 @@ wheels = [ { 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]] name = "ruff" version = "0.9.9" diff --git a/server/src/constants.ts b/server/src/constants.ts index 3e946578ab..6c0319fcee 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -96,6 +96,18 @@ export const CLIP_MODEL_INFO: Record = { 'ViT-SO400M-14-SigLIP-384__webli': { dimSize: 1152 }, 'nllb-clip-large-siglip__mrl': { dimSize: 1152 }, 'nllb-clip-large-siglip__v1': { dimSize: 1152 }, + 'ViT-B-16-SigLIP2__webli': { dimSize: 768 }, + 'ViT-B-32-SigLIP2-256__webli': { dimSize: 768 }, + 'ViT-L-16-SigLIP2-256__webli': { dimSize: 1024 }, + 'ViT-L-16-SigLIP2-384__webli': { dimSize: 1024 }, + 'ViT-L-16-SigLIP2-512__webli': { dimSize: 1024 }, + 'ViT-SO400M-14-SigLIP2__webli': { dimSize: 1152 }, + 'ViT-SO400M-14-SigLIP2-378__webli': { dimSize: 1152 }, + 'ViT-SO400M-16-SigLIP2-256__webli': { dimSize: 1152 }, + 'ViT-SO400M-16-SigLIP2-384__webli': { dimSize: 1152 }, + 'ViT-SO400M-16-SigLIP2-512__webli': { dimSize: 1152 }, + 'ViT-gopt-16-SigLIP2-256__webli': { dimSize: 1536 }, + 'ViT-gopt-16-SigLIP2-384__webli': { dimSize: 1536 }, }; type SharpRotationData = { From 0bb95544e57f9b9a6ec2992da97e785ef236ad32 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 17 Mar 2025 12:30:13 -0400 Subject: [PATCH 03/18] chore: pin github action digests (#16875) --- renovate.json | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json b/renovate.json index c65ad02754..91d1bbbecc 100644 --- a/renovate.json +++ b/renovate.json @@ -85,6 +85,7 @@ ".github/**" ], "groupName": "github-actions", + "pinDigests": true, "schedule": "on tuesday" }, { From 0a8135dde453c0c3fa7a8ffaf3e2923cc2a25977 Mon Sep 17 00:00:00 2001 From: bo0tzz Date: Mon, 17 Mar 2025 18:13:43 +0100 Subject: [PATCH 04/18] fix: docker workflow for rknn (#16922) * fix: specify gha runner for rknn * fix: remove s from platforms * fix: merge job for rknn --- .github/workflows/docker.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5d19e5b90f..4ff2454f0a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -129,7 +129,9 @@ jobs: runner: ubuntu-24.04-arm device: armnn suffix: -armnn - - platforms: linux/arm64 + + - platform: linux/arm64 + runner: ubuntu-24.04-arm device: rknn suffix: -rknn @@ -222,6 +224,8 @@ jobs: suffix: -openvino - device: armnn suffix: -armnn + - device: rknn + suffix: -rknn needs: - build_and_push_ml steps: @@ -457,4 +461,4 @@ jobs: run: exit 1 - name: All jobs passed or skipped if: ${{ !(contains(needs.*.result, 'failure')) }} - run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}" \ No newline at end of file + run: echo "All jobs passed or skipped" && echo "${{ toJSON(needs.*.result) }}" From 9105e696bfee6e34d0513a70b5295a3fa25774e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:25:14 +0000 Subject: [PATCH 05/18] chore(deps): pin github action dependencies (#16923) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-mobile.yml | 12 ++--- .github/workflows/cache-cleanup.yml | 2 +- .github/workflows/cli.yml | 16 +++--- .github/workflows/codeql-analysis.yml | 8 +-- .github/workflows/docker.yml | 48 +++++++++--------- .github/workflows/docs-build.yml | 10 ++-- .github/workflows/docs-deploy.yml | 20 ++++---- .github/workflows/docs-destroy.yml | 6 +-- .github/workflows/fix-format.yml | 10 ++-- .github/workflows/pr-label-validation.yml | 2 +- .github/workflows/pr-labeler.yml | 2 +- .github/workflows/prepare-release.yml | 16 +++--- .github/workflows/preview-label.yaml | 4 +- .github/workflows/sdk.yml | 4 +- .github/workflows/static_analysis.yml | 10 ++-- .github/workflows/test.yml | 62 +++++++++++------------ .github/workflows/weblate-lock.yml | 6 +-- 17 files changed, 119 insertions(+), 119 deletions(-) diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 6e3597b2f1..37e0ebc50d 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -22,9 +22,9 @@ jobs: should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: found_paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: filters: | mobile: @@ -51,18 +51,18 @@ jobs: ref="${input_ref:-$github_ref}" echo "ref=$ref" >> $GITHUB_OUTPUT - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: ref: ${{ steps.get-ref.outputs.ref }} - - uses: actions/setup-java@v4 + - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 with: distribution: 'zulu' java-version: '17' cache: 'gradle' - name: Setup Flutter SDK - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 with: channel: 'stable' 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 - name: Publish Android Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: release-apk-signed path: mobile/build/app/outputs/flutter-apk/*.apk diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index 8b89cba107..0cc73c46c3 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Cleanup run: | diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 1243a81105..d0396260ce 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -29,9 +29,9 @@ jobs: working-directory: ./cli steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './cli/.nvmrc' registry-url: 'https://registry.npmjs.org' @@ -53,16 +53,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v3.6.0 + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.10.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io @@ -77,7 +77,7 @@ jobs: - name: Generate docker image tags id: metadata - uses: docker/metadata-action@v5 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 with: flavor: | latest=false @@ -88,7 +88,7 @@ jobs: type=raw,value=latest,enable=${{ github.event_name == 'release' }} - name: Build and push image - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: file: cli/Dockerfile platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 03b8364a3a..0755a0670b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,11 +42,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -60,7 +60,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@v3 + uses: github/codeql-action/autobuild@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3 # ℹ️ 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 @@ -73,6 +73,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4ff2454f0a..4007b07e2e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -23,9 +23,9 @@ jobs: should_run_ml: ${{ steps.found_paths.outputs.machine-learning == 'true' || steps.should_force.outputs.should_force == 'true' }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: found_paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: filters: | server: @@ -52,7 +52,7 @@ jobs: suffix: ["", "-cuda", "-openvino", "-armnn","-rknn"] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -77,7 +77,7 @@ jobs: suffix: [""] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -142,13 +142,13 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.10.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io @@ -175,7 +175,7 @@ jobs: - name: Build and push image id: build - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: ${{ env.context }} file: ${{ env.file }} @@ -200,7 +200,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: ml-digests-${{ matrix.device }}-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -230,7 +230,7 @@ jobs: - build_and_push_ml steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: path: ${{ runner.temp }}/digests pattern: ml-digests-${{ matrix.device }}-* @@ -238,24 +238,24 @@ jobs: - name: Login to Docker Hub if: ${{ github.event_name == 'release' }} - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - name: Generate docker image tags id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 env: DOCKER_METADATA_PR_HEAD_SHA: "true" with: @@ -308,13 +308,13 @@ jobs: echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io @@ -341,7 +341,7 @@ jobs: - name: Build and push image id: build - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: ${{ env.context }} file: ${{ env.file }} @@ -366,7 +366,7 @@ jobs: touch "${{ runner.temp }}/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: server-digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* @@ -384,7 +384,7 @@ jobs: - build_and_push_server steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: path: ${{ runner.temp }}/digests pattern: server-digests-* @@ -392,24 +392,24 @@ jobs: - name: Login to Docker Hub if: ${{ github.event_name == 'release' }} - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - name: Generate docker image tags id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 env: DOCKER_METADATA_PR_HEAD_SHA: "true" with: diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 63b906748f..c07a3058ed 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -18,9 +18,9 @@ jobs: should_run: ${{ steps.found_paths.outputs.docs == 'true' || steps.should_force.outputs.should_force == 'true' }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: found_paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: filters: | docs: @@ -42,10 +42,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './docs/.nvmrc' @@ -59,7 +59,7 @@ jobs: run: npm run build - name: Upload build output - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: docs-build-output path: docs/build/ diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index ab197fa459..6b3ddba01d 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -17,7 +17,7 @@ jobs: run: echo 'The triggering workflow did not succeed' && exit 1 - name: Get artifact id: get-artifact - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 with: script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ @@ -35,7 +35,7 @@ jobs: return { found: true, id: matchArtifact.id }; - name: Determine deploy parameters id: parameters - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 with: script: | 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 }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Load parameters id: parameters - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 with: script: | const json = `${{ needs.checks.outputs.parameters }}`; @@ -115,7 +115,7 @@ jobs: echo "Starting docs deployment for ${{ steps.parameters.outputs.event }} ${{ steps.parameters.outputs.name }}" - name: Download artifact - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 with: script: | let artifact = ${{ needs.checks.outputs.artifact }}; @@ -138,7 +138,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@v2 + uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 with: tg_version: "0.58.12" tofu_version: "1.7.1" @@ -153,7 +153,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@v2 + uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 with: tg_version: "0.58.12" tofu_version: "1.7.1" @@ -167,7 +167,7 @@ jobs: echo "output=$TG_OUT" >> $GITHUB_OUTPUT - name: Publish to Cloudflare Pages - uses: cloudflare/pages-action@v1 + uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} @@ -184,7 +184,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@v2 + uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 with: tg_version: '0.58.12' tofu_version: '1.7.1' @@ -192,7 +192,7 @@ jobs: tg_command: 'apply' - name: Comment - uses: actions-cool/maintain-one-comment@v3 + uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3 if: ${{ steps.parameters.outputs.event == 'pr' }} with: number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }} diff --git a/.github/workflows/docs-destroy.yml b/.github/workflows/docs-destroy.yml index f9e69b135a..2f8b218093 100644 --- a/.github/workflows/docs-destroy.yml +++ b/.github/workflows/docs-destroy.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Destroy Docs Subdomain env: @@ -18,7 +18,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} TF_STATE_POSTGRES_CONN_STR: ${{ secrets.TF_STATE_POSTGRES_CONN_STR }} - uses: gruntwork-io/terragrunt-action@v2 + uses: gruntwork-io/terragrunt-action@9559e51d05873b0ea467c42bbabcb5c067642ccc # v2 with: tg_version: "0.58.12" tofu_version: "1.7.1" @@ -26,7 +26,7 @@ jobs: tg_command: "destroy -refresh=false" - name: Comment - uses: actions-cool/maintain-one-comment@v3 + uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3 with: number: ${{ github.event.number }} delete: true diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index 0c630c9e4b..00eb6feae4 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -13,19 +13,19 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: ref: ${{ github.event.pull_request.head.ref }} token: ${{ steps.generate-token.outputs.token }} - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './server/.nvmrc' @@ -33,13 +33,13 @@ jobs: run: make install-all && make format-all - name: Commit and push - uses: EndBug/add-and-commit@v9 + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9 with: default_author: github_actions message: 'chore: fix formatting' - name: Remove label - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 if: always() with: script: | diff --git a/.github/workflows/pr-label-validation.yml b/.github/workflows/pr-label-validation.yml index 0abbc01afd..1806b0a699 100644 --- a/.github/workflows/pr-label-validation.yml +++ b/.github/workflows/pr-label-validation.yml @@ -12,7 +12,7 @@ jobs: pull-requests: write steps: - name: Require PR to have a changelog label - uses: mheap/github-action-required-labels@v5 + uses: mheap/github-action-required-labels@388fd6af37b34cdfe5a23b37060e763217e58b03 # v5 with: mode: exactly count: 1 diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index e57cd86e2b..b1cdfcf47d 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -9,4 +9,4 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index df4856b1a1..1855d3be29 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -31,25 +31,25 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: token: ${{ steps.generate-token.outputs.token }} - name: Install uv - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5 - name: Bump version run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}" - name: Commit and tag id: push-tag - uses: EndBug/add-and-commit@v9 + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9 with: default_author: github_actions message: 'chore: version ${{ env.IMMICH_VERSION }}' @@ -70,23 +70,23 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@v1 + uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: token: ${{ steps.generate-token.outputs.token }} - name: Download APK - uses: actions/download-artifact@v4 + uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: name: release-apk-signed - name: Create draft release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2 with: draft: true tag_name: ${{ env.IMMICH_VERSION }} diff --git a/.github/workflows/preview-label.yaml b/.github/workflows/preview-label.yaml index 1c324ab49f..be244f2e6d 100644 --- a/.github/workflows/preview-label.yaml +++ b/.github/workflows/preview-label.yaml @@ -11,7 +11,7 @@ jobs: permissions: pull-requests: write steps: - - uses: mshick/add-pr-comment@v2 + - uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 with: message-id: "preview-status" message: "Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.cloud/" @@ -22,7 +22,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 with: script: | github.rest.issues.removeLabel({ diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index e581da4b16..64366fc0b0 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -15,9 +15,9 @@ jobs: run: working-directory: ./open-api/typescript-sdk steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v4 + - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './open-api/typescript-sdk/.nvmrc' registry-url: 'https://registry.npmjs.org' diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 1e2020a19d..7f03dedd72 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -16,9 +16,9 @@ jobs: should_run: ${{ steps.found_paths.outputs.mobile == 'true' || steps.should_force.outputs.should_force == 'true' }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: found_paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: filters: | mobile: @@ -38,10 +38,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Flutter SDK - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 with: channel: 'stable' flutter-version-file: ./mobile/pubspec.yaml @@ -55,7 +55,7 @@ jobs: working-directory: ./mobile - name: Find file changes - uses: tj-actions/verify-changed-files@v20 + uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 id: verify-changed-files with: files: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99f41697d4..b3df4931ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,9 +23,9 @@ jobs: 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' }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: found_paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: filters: | web: @@ -61,10 +61,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './server/.nvmrc' @@ -98,10 +98,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './cli/.nvmrc' @@ -139,10 +139,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './cli/.nvmrc' @@ -173,10 +173,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './web/.nvmrc' @@ -218,10 +218,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './e2e/.nvmrc' @@ -257,10 +257,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './server/.nvmrc' @@ -282,12 +282,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: submodules: 'recursive' - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './e2e/.nvmrc' @@ -324,12 +324,12 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: submodules: 'recursive' - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './e2e/.nvmrc' @@ -360,9 +360,9 @@ jobs: if: ${{ needs.pre-job.outputs.should_run_mobile == 'true' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Flutter SDK - uses: subosito/flutter-action@v2 + uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # v2 with: channel: 'stable' flutter-version-file: ./mobile/pubspec.yaml @@ -379,10 +379,10 @@ jobs: run: working-directory: ./machine-learning steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install uv - uses: astral-sh/setup-uv@v5 - - uses: actions/setup-python@v5 + uses: astral-sh/setup-uv@f94ec6bedd8674c4426838e6b50417d36b6ab231 # v5 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 # TODO: add caching when supported (https://github.com/actions/setup-python/pull/818) # with: # python-version: 3.11 @@ -407,7 +407,7 @@ jobs: name: ShellCheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master with: @@ -421,10 +421,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './server/.nvmrc' @@ -438,7 +438,7 @@ jobs: run: make open-api - name: Find file changes - uses: tj-actions/verify-changed-files@v20 + uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 id: verify-changed-files with: files: | @@ -476,10 +476,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4 with: node-version-file: './server/.nvmrc' @@ -500,7 +500,7 @@ jobs: run: npm run typeorm:migrations:generate ./src/migrations/TestMigration - name: Find file changes - uses: tj-actions/verify-changed-files@v20 + uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 id: verify-changed-files with: files: | @@ -519,7 +519,7 @@ jobs: DB_URL: postgres://postgres:postgres@localhost:5432/immich - name: Find file changes - uses: tj-actions/verify-changed-files@v20 + uses: tj-actions/verify-changed-files@6ed7632824d235029086612d4330d659005af687 # v20 id: verify-changed-sql-files with: files: | diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index 4189e51919..de43cda1f1 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -11,9 +11,9 @@ jobs: should_run: ${{ steps.found_paths.outputs.i18n == 'true' && github.head_ref != 'chore/translations'}} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: found_paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: filters: | i18n: @@ -36,7 +36,7 @@ jobs: exit 1 fi - name: Find Pull Request - uses: juliangruber/find-pull-request-action@v1 + uses: juliangruber/find-pull-request-action@48b6133aa6c826f267ebd33aa2d29470f9d9e7d0 # v1 id: find-pr with: branch: chore/translations From f8b40188e23d0ad1e3b29be18ddcbccbded39a95 Mon Sep 17 00:00:00 2001 From: Yaros Date: Mon, 17 Mar 2025 18:34:58 +0100 Subject: [PATCH 06/18] fix(mobile): change share icons for consistency (#16904) --- mobile/assets/i18n/en-US.json | 2 +- mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index bd2397ce3b..a6f484a0fb 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -197,7 +197,7 @@ "control_bottom_app_bar_edit_time": "Edit Date & Time", "control_bottom_app_bar_favorite": "Favorite", "control_bottom_app_bar_share": "Share", - "control_bottom_app_bar_share_to": "Share To", + "control_bottom_app_bar_share_link": "Share Link", "control_bottom_app_bar_stack": "Stack", "control_bottom_app_bar_trash_from_immich": "Move to Trash", "control_bottom_app_bar_unarchive": "Unarchive", diff --git a/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart b/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart index ec054d08ee..794f7627df 100644 --- a/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart +++ b/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart @@ -127,12 +127,12 @@ class ControlBottomAppBar extends HookConsumerWidget { ControlBoxButton( iconData: Icons.share_rounded, label: "control_bottom_app_bar_share".tr(), - onPressed: enabled ? () => onShare(false) : null, + onPressed: enabled ? () => onShare(true) : null, ), ControlBoxButton( - iconData: Icons.ios_share_rounded, - label: "control_bottom_app_bar_share_to".tr(), - onPressed: enabled ? () => onShare(true) : null, + iconData: Icons.link_rounded, + label: "control_bottom_app_bar_share_link".tr(), + onPressed: enabled ? () => onShare(false) : null, ), if (hasRemote && onArchive != null) ControlBoxButton( From d0e283f687fd190d01f76c2e0d171f382e64f2b0 Mon Sep 17 00:00:00 2001 From: Abhinav Valecha Date: Mon, 17 Mar 2025 23:27:59 +0530 Subject: [PATCH 07/18] feat(server): version command for immich-admin #9611 (#16924) * feat(server): Add version command for immich-admin #9611 * chore: clean up --------- Co-authored-by: Jason Rasmussen --- docs/docs/administration/server-commands.md | 8 +++++++ server/src/commands/index.ts | 2 ++ server/src/commands/version.command.ts | 24 +++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 server/src/commands/version.command.ts diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md index 355ee10e39..b414f5deaa 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -11,6 +11,7 @@ The `immich-server` docker image comes preinstalled with an administrative CLI ( | `enable-oauth-login` | Enable OAuth login | | `disable-oauth-login` | Disable OAuth login | | `list-users` | List Immich users | +| `version` | Print Immich version | ## How to run a command @@ -80,3 +81,10 @@ immich-admin list-users } ] ``` + +Print Immich Version + +``` +immich-admin version +v1.129.0 +``` diff --git a/server/src/commands/index.ts b/server/src/commands/index.ts index 016a26cb34..59846628bf 100644 --- a/server/src/commands/index.ts +++ b/server/src/commands/index.ts @@ -2,6 +2,7 @@ import { ListUsersCommand } from 'src/commands/list-users.command'; import { DisableOAuthLogin, EnableOAuthLogin } from 'src/commands/oauth-login'; import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from 'src/commands/password-login'; import { PromptPasswordQuestions, ResetAdminPasswordCommand } from 'src/commands/reset-admin-password.command'; +import { VersionCommand } from 'src/commands/version.command'; export const commands = [ ResetAdminPasswordCommand, @@ -11,4 +12,5 @@ export const commands = [ EnableOAuthLogin, DisableOAuthLogin, ListUsersCommand, + VersionCommand, ]; diff --git a/server/src/commands/version.command.ts b/server/src/commands/version.command.ts new file mode 100644 index 0000000000..585f097b6a --- /dev/null +++ b/server/src/commands/version.command.ts @@ -0,0 +1,24 @@ +import { Command, CommandRunner } from 'nest-commander'; +import { VersionService } from 'src/services/version.service'; + +@Command({ + name: 'version', + description: 'Print Immich version', +}) +export class VersionCommand extends CommandRunner { + constructor(private service: VersionService) { + super(); + } + + run(): Promise { + try { + const version = this.service.getVersion(); + console.log(`v${version.major}.${version.minor}.${version.patch}`); + } catch (error) { + console.error(error); + console.error('Unable to get version'); + } + + return Promise.resolve(); + } +} From 3ce8608662fb7faec15c56eab063a384e29e3087 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 14:07:09 -0400 Subject: [PATCH 08/18] chore(deps): update mcr.microsoft.com/devcontainers/typescript-node:22 docker digest to 2ef2373 (#16925) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9dd57a5ec8..670d11a06b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:9791f4aa527774bc370c6bd2f6705ce5a686f1e6f204badd8dfaacce28c631ae +ARG BASEIMAGE=mcr.microsoft.com/devcontainers/typescript-node:22@sha256:2ef23730ec68d8511ec8e6e0b82550ca728b256805d81f60ed890f3bfb21cfb9 FROM ${BASEIMAGE} # Flutter SDK From 93907a89d83b0e7b34fe6ad31a6d2ed26936a03a Mon Sep 17 00:00:00 2001 From: Yaros Date: Mon, 17 Mar 2025 19:51:17 +0100 Subject: [PATCH 09/18] fix(mobile): age calculation & formatting (#16833) --- mobile/assets/i18n/en-US.json | 4 +- .../detail_panel/people_info.dart | 85 +++++++++++-------- .../widgets/search/curated_people_row.dart | 67 ++++++++------- 3 files changed, 87 insertions(+), 69 deletions(-) diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index a6f484a0fb..651780ca16 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -264,7 +264,9 @@ "exif_bottom_sheet_location_add": "Add a location", "exif_bottom_sheet_people": "PEOPLE", "exif_bottom_sheet_person_add_person": "Add name", - "exif_bottom_sheet_person_age": "Age {}", + "exif_bottom_sheet_person_age_years": "Age {}", + "exif_bottom_sheet_person_age_year_months": "Age 1 year, {} months", + "exif_bottom_sheet_person_age_months": "Age {} months", "experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_subtitle": "Use at your own risk!", diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart index 2e868682f8..712e939ad5 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart @@ -1,5 +1,3 @@ -import 'dart:math' as math; - import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -26,7 +24,6 @@ class PeopleInfo extends ConsumerWidget { .watch(assetPeopleNotifierProvider(asset)) .value ?.where((p) => !p.isHidden); - final double imageSize = math.min(context.width / 3, 150); showPersonNameEditModel( String personId, @@ -48,12 +45,9 @@ class PeopleInfo extends ConsumerWidget { (p) => SearchCuratedContent( id: p.id, label: p.name, - subtitle: p.birthDate != null - ? "exif_bottom_sheet_person_age".tr( - args: [ - _calculateAge(p.birthDate!).toString(), - ], - ) + subtitle: p.birthDate != null && + p.birthDate!.isBefore(asset.fileCreatedAt) + ? _formatAge(p.birthDate!, asset.fileCreatedAt) : null, ), ) @@ -83,27 +77,24 @@ class PeopleInfo extends ConsumerWidget { ).tr(), ), ), - SizedBox( - height: imageSize, - child: Padding( - padding: const EdgeInsets.only(top: 16.0), - child: CuratedPeopleRow( - padding: padding, - content: curatedPeople, - onTap: (content, index) { - context - .pushRoute( - PersonResultRoute( - personId: content.id, - personName: content.label, - ), - ) - .then((_) => peopleProvider.refresh()); - }, - onNameTap: (person, index) => { - showPersonNameEditModel(person.id, person.label), - }, - ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: CuratedPeopleRow( + padding: padding, + content: curatedPeople, + onTap: (content, index) { + context + .pushRoute( + PersonResultRoute( + personId: content.id, + personName: content.label, + ), + ) + .then((_) => peopleProvider.refresh()); + }, + onNameTap: (person, index) => { + showPersonNameEditModel(person.id, person.label), + }, ), ), ], @@ -112,16 +103,36 @@ class PeopleInfo extends ConsumerWidget { ); } - int _calculateAge(DateTime birthDate) { - DateTime today = DateTime.now(); - int age = today.year - birthDate.year; + String _formatAge(DateTime birthDate, DateTime referenceDate) { + int ageInYears = _calculateAge(birthDate, referenceDate); + int ageInMonths = _calculateAgeInMonths(birthDate, referenceDate); - // Check if the birthday has occurred this year - if (today.month < birthDate.month || - (today.month == birthDate.month && today.day < birthDate.day)) { + if (ageInMonths <= 11) { + return "exif_bottom_sheet_person_age_months" + .tr(args: [ageInMonths.toString()]); + } else if (ageInMonths > 12 && ageInMonths <= 23) { + return "exif_bottom_sheet_person_age_year_months" + .tr(args: [(ageInMonths - 12).toString()]); + } else { + return "exif_bottom_sheet_person_age_years" + .tr(args: [ageInYears.toString()]); + } + } + + int _calculateAge(DateTime birthDate, DateTime referenceDate) { + int age = referenceDate.year - birthDate.year; + if (referenceDate.month < birthDate.month || + (referenceDate.month == birthDate.month && + referenceDate.day < birthDate.day)) { age--; } - return age; } + + int _calculateAgeInMonths(DateTime birthDate, DateTime referenceDate) { + return (referenceDate.year - birthDate.year) * 12 + + referenceDate.month - + birthDate.month - + (referenceDate.day < birthDate.day ? 1 : 0); + } } diff --git a/mobile/lib/widgets/search/curated_people_row.dart b/mobile/lib/widgets/search/curated_people_row.dart index eece328392..10c19c7e60 100644 --- a/mobile/lib/widgets/search/curated_people_row.dart +++ b/mobile/lib/widgets/search/curated_people_row.dart @@ -26,43 +26,48 @@ class CuratedPeopleRow extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - height: imageSize + 50, - child: ListView.separated( + width: double.infinity, + child: SingleChildScrollView( padding: padding, scrollDirection: Axis.horizontal, - separatorBuilder: (context, index) => const SizedBox(width: 16), - itemBuilder: (context, index) { - final person = content[index]; - final headers = ApiService.getRequestHeaders(); - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - GestureDetector( - onTap: () => onTap?.call(person, index), - child: SizedBox( - height: imageSize, - child: Material( - shape: const CircleBorder(side: BorderSide.none), - elevation: 3, - child: CircleAvatar( - maxRadius: imageSize / 2, - backgroundImage: NetworkImage( - getFaceThumbnailUrl(person.id), - headers: headers, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(content.length, (index) { + final person = content[index]; + final headers = ApiService.getRequestHeaders(); + return Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => onTap?.call(person, index), + child: SizedBox( + height: imageSize, + child: Material( + shape: const CircleBorder(side: BorderSide.none), + elevation: 3, + child: CircleAvatar( + maxRadius: imageSize / 2, + backgroundImage: NetworkImage( + getFaceThumbnailUrl(person.id), + headers: headers, + ), + ), ), ), ), - ), + const SizedBox(height: 8), + SizedBox( + width: imageSize, + child: _buildPersonLabel(context, person, index), + ), + ], ), - const SizedBox(height: 8), - SizedBox( - width: imageSize, - child: _buildPersonLabel(context, person, index), - ), - ], - ); - }, - itemCount: content.length, + ); + }), + ), ), ); } From 6a40aa83b723da300c453b531a583b4cda05fb28 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 17 Mar 2025 15:32:12 -0400 Subject: [PATCH 10/18] refactor: better types for getList and getDeletedAfter (#16926) --- server/src/database.ts | 38 +++- server/src/dtos/user.dto.ts | 23 +- server/src/queries/user.repository.sql | 205 +++++++++++++----- .../src/repositories/activity.repository.ts | 2 +- server/src/repositories/partner.repository.ts | 7 +- server/src/repositories/user.repository.ts | 55 ++--- server/src/services/partner.service.ts | 4 +- server/src/services/user.service.spec.ts | 95 ++++---- server/src/services/user.service.ts | 10 +- server/test/factory.ts | 3 + server/test/fixtures/system-config.stub.ts | 5 - server/test/medium/specs/user.service.spec.ts | 64 +++++- server/test/small.factory.ts | 25 ++- 13 files changed, 342 insertions(+), 194 deletions(-) diff --git a/server/src/database.ts b/server/src/database.ts index 9caa0c196e..7fd791c59c 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -1,5 +1,5 @@ -import { sql } from 'kysely'; -import { AssetStatus, AssetType, Permission } from 'src/enum'; +import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; +import { AssetStatus, AssetType, Permission, UserStatus } from 'src/enum'; export type AuthUser = { id: string; @@ -46,6 +46,20 @@ export type User = { profileChangedAt: Date; }; +export type UserAdmin = User & { + storageLabel: string | null; + shouldChangePassword: boolean; + isAdmin: boolean; + createdAt: Date; + updatedAt: Date; + deletedAt: Date | null; + oauthId: string; + quotaSizeInBytes: number | null; + quotaUsageInBytes: number; + status: UserStatus; + metadata: UserMetadataEntity[]; +}; + export type Asset = { createdAt: Date; updatedAt: Date; @@ -103,9 +117,9 @@ export type Partner = { inTimeline: boolean; }; +const userColumns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const; + export const columns = { - ackEpoch: (columnName: 'createdAt' | 'updatedAt' | 'deletedAt') => - sql.raw(`extract(epoch from "${columnName}")::text`).as('ackEpoch'), authUser: [ 'users.id', 'users.name', @@ -125,7 +139,21 @@ export const columns = { 'shared_links.allowDownload', 'shared_links.password', ], - userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'], + user: userColumns, + userAdmin: [ + ...userColumns, + 'createdAt', + 'updatedAt', + 'deletedAt', + 'isAdmin', + 'status', + 'oauthId', + 'profileImagePath', + 'shouldChangePassword', + 'storageLabel', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + ], tagDto: ['id', 'value', 'createdAt', 'updatedAt', 'color', 'parentId'], apiKey: ['id', 'name', 'userId', 'createdAt', 'updatedAt', 'permissions'], syncAsset: [ diff --git a/server/src/dtos/user.dto.ts b/server/src/dtos/user.dto.ts index 5b7784aa3d..0177e9b475 100644 --- a/server/src/dtos/user.dto.ts +++ b/server/src/dtos/user.dto.ts @@ -1,8 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator'; -import { User } from 'src/database'; -import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; +import { User, UserAdmin } from 'src/database'; +import { UserMetadataEntity, UserMetadataItem } from 'src/entities/user-metadata.entity'; import { UserEntity } from 'src/entities/user.entity'; import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum'; import { getPreferences } from 'src/utils/preferences'; @@ -42,28 +42,17 @@ export class UserLicense { activatedAt!: Date; } -export const mapUser = (entity: UserEntity): UserResponseDto => { +export const mapUser = (entity: UserEntity | User): UserResponseDto => { return { id: entity.id, email: entity.email, name: entity.name, profileImagePath: entity.profileImagePath, - avatarColor: getPreferences(entity.email, entity.metadata || []).avatar.color, + avatarColor: getPreferences(entity.email, (entity as UserEntity).metadata || []).avatar.color, profileChangedAt: entity.profileChangedAt, }; }; -export const mapDatabaseUser = (user: User): UserResponseDto => { - return { - id: user.id, - email: user.email, - name: user.name, - profileImagePath: user.profileImagePath, - avatarColor: getPreferences(user.email, []).avatar.color, - profileChangedAt: user.profileChangedAt, - }; -}; - export class UserAdminSearchDto { @ValidateBoolean({ optional: true }) withDeleted?: boolean; @@ -153,8 +142,8 @@ export class UserAdminResponseDto extends UserResponseDto { license!: UserLicense | null; } -export function mapUserAdmin(entity: UserEntity): UserAdminResponseDto { - const license = entity.metadata?.find( +export function mapUserAdmin(entity: UserEntity | UserAdmin): UserAdminResponseDto { + const license = (entity.metadata as UserMetadataItem[])?.find( (item): item is UserMetadataEntity => item.key === UserMetadataKey.LICENSE, )?.value; return { diff --git a/server/src/queries/user.repository.sql b/server/src/queries/user.repository.sql index 7ae8003a09..a726ab46e9 100644 --- a/server/src/queries/user.repository.sql +++ b/server/src/queries/user.repository.sql @@ -3,20 +3,21 @@ -- UserRepository.get select "id", - "email", - "createdAt", - "profileImagePath", - "isAdmin", - "shouldChangePassword", - "deletedAt", - "oauthId", - "updatedAt", - "storageLabel", "name", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", + "status", + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", "quotaSizeInBytes", "quotaUsageInBytes", - "status", - "profileChangedAt", ( select coalesce(json_agg(agg), '[]') @@ -39,20 +40,21 @@ where -- UserRepository.getAdmin select "id", - "email", - "createdAt", - "profileImagePath", - "isAdmin", - "shouldChangePassword", - "deletedAt", - "oauthId", - "updatedAt", - "storageLabel", "name", - "quotaSizeInBytes", - "quotaUsageInBytes", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", "status", - "profileChangedAt" + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", + "quotaSizeInBytes", + "quotaUsageInBytes" from "users" where @@ -71,20 +73,21 @@ where -- UserRepository.getByEmail select "id", - "email", - "createdAt", - "profileImagePath", - "isAdmin", - "shouldChangePassword", - "deletedAt", - "oauthId", - "updatedAt", - "storageLabel", "name", - "quotaSizeInBytes", - "quotaUsageInBytes", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", "status", - "profileChangedAt" + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", + "quotaSizeInBytes", + "quotaUsageInBytes" from "users" where @@ -94,20 +97,21 @@ where -- UserRepository.getByStorageLabel select "id", - "email", - "createdAt", - "profileImagePath", - "isAdmin", - "shouldChangePassword", - "deletedAt", - "oauthId", - "updatedAt", - "storageLabel", "name", - "quotaSizeInBytes", - "quotaUsageInBytes", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", "status", - "profileChangedAt" + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", + "quotaSizeInBytes", + "quotaUsageInBytes" from "users" where @@ -117,26 +121,109 @@ where -- UserRepository.getByOAuthId select "id", - "email", - "createdAt", - "profileImagePath", - "isAdmin", - "shouldChangePassword", - "deletedAt", - "oauthId", - "updatedAt", - "storageLabel", "name", - "quotaSizeInBytes", - "quotaUsageInBytes", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", "status", - "profileChangedAt" + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", + "quotaSizeInBytes", + "quotaUsageInBytes" from "users" where "users"."oauthId" = $1 and "users"."deletedAt" is null +-- UserRepository.getDeletedAfter +select + "id" +from + "users" +where + "users"."deletedAt" < $1 + +-- UserRepository.getList (with deleted) +select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", + "status", + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", + "quotaSizeInBytes", + "quotaUsageInBytes", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "user_metadata".* + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as agg + ) as "metadata" +from + "users" +order by + "createdAt" desc + +-- UserRepository.getList (without deleted) +select + "id", + "name", + "email", + "profileImagePath", + "profileChangedAt", + "createdAt", + "updatedAt", + "deletedAt", + "isAdmin", + "status", + "oauthId", + "profileImagePath", + "shouldChangePassword", + "storageLabel", + "quotaSizeInBytes", + "quotaUsageInBytes", + ( + select + coalesce(json_agg(agg), '[]') + from + ( + select + "user_metadata".* + from + "user_metadata" + where + "users"."id" = "user_metadata"."userId" + ) as agg + ) as "metadata" +from + "users" +where + "users"."deletedAt" is null +order by + "createdAt" desc + -- UserRepository.getUserStats select "users"."id" as "userId", diff --git a/server/src/repositories/activity.repository.ts b/server/src/repositories/activity.repository.ts index d998fea23c..48def82f49 100644 --- a/server/src/repositories/activity.repository.ts +++ b/server/src/repositories/activity.repository.ts @@ -18,7 +18,7 @@ const withUser = (eb: ExpressionBuilder) => { return jsonObjectFrom( eb .selectFrom('users') - .select(columns.userDto) + .select(columns.user) .whereRef('users.id', '=', 'activity.userId') .where('users.deletedAt', 'is', null), ).as('user'); diff --git a/server/src/repositories/partner.repository.ts b/server/src/repositories/partner.repository.ts index 8877185d31..489793aa77 100644 --- a/server/src/repositories/partner.repository.ts +++ b/server/src/repositories/partner.repository.ts @@ -18,16 +18,13 @@ export enum PartnerDirection { const withSharedBy = (eb: ExpressionBuilder) => { return jsonObjectFrom( - eb.selectFrom('users as sharedBy').select(columns.userDto).whereRef('sharedBy.id', '=', 'partners.sharedById'), + eb.selectFrom('users as sharedBy').select(columns.user).whereRef('sharedBy.id', '=', 'partners.sharedById'), ).as('sharedBy'); }; const withSharedWith = (eb: ExpressionBuilder) => { return jsonObjectFrom( - eb - .selectFrom('users as sharedWith') - .select(columns.userDto) - .whereRef('sharedWith.id', '=', 'partners.sharedWithId'), + eb.selectFrom('users as sharedWith').select(columns.user).whereRef('sharedWith.id', '=', 'partners.sharedWithId'), ).as('sharedWith'); }; diff --git a/server/src/repositories/user.repository.ts b/server/src/repositories/user.repository.ts index 1387828be6..055df9dfdc 100644 --- a/server/src/repositories/user.repository.ts +++ b/server/src/repositories/user.repository.ts @@ -1,6 +1,8 @@ import { Injectable } from '@nestjs/common'; import { Insertable, Kysely, sql, Updateable } from 'kysely'; +import { DateTime } from 'luxon'; import { InjectKysely } from 'nestjs-kysely'; +import { columns, UserAdmin } from 'src/database'; import { DB, UserMetadata as DbUserMetadata, Users } from 'src/db'; import { DummyValue, GenerateSql } from 'src/decorators'; import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity'; @@ -8,24 +10,6 @@ import { UserEntity, withMetadata } from 'src/entities/user.entity'; import { AssetType, UserStatus } from 'src/enum'; import { asUuid } from 'src/utils/database'; -const columns = [ - 'id', - 'email', - 'createdAt', - 'profileImagePath', - 'isAdmin', - 'shouldChangePassword', - 'deletedAt', - 'oauthId', - 'updatedAt', - 'storageLabel', - 'name', - 'quotaSizeInBytes', - 'quotaUsageInBytes', - 'status', - 'profileChangedAt', -] as const; - type Upsert = Insertable; export interface UserListFilter { @@ -57,7 +41,7 @@ export class UserRepository { return this.db .selectFrom('users') - .select(columns) + .select(columns.userAdmin) .select(withMetadata) .where('users.id', '=', userId) .$if(!options.withDeleted, (eb) => eb.where('users.deletedAt', 'is', null)) @@ -76,7 +60,7 @@ export class UserRepository { getAdmin(): Promise { return this.db .selectFrom('users') - .select(columns) + .select(columns.userAdmin) .where('users.isAdmin', '=', true) .where('users.deletedAt', 'is', null) .executeTakeFirst() as Promise; @@ -98,7 +82,7 @@ export class UserRepository { getByEmail(email: string, withPassword?: boolean): Promise { return this.db .selectFrom('users') - .select(columns) + .select(columns.userAdmin) .$if(!!withPassword, (eb) => eb.select('password')) .where('email', '=', email) .where('users.deletedAt', 'is', null) @@ -109,7 +93,7 @@ export class UserRepository { getByStorageLabel(storageLabel: string): Promise { return this.db .selectFrom('users') - .select(columns) + .select(columns.userAdmin) .where('users.storageLabel', '=', storageLabel) .where('users.deletedAt', 'is', null) .executeTakeFirst() as Promise; @@ -119,35 +103,36 @@ export class UserRepository { getByOAuthId(oauthId: string): Promise { return this.db .selectFrom('users') - .select(columns) + .select(columns.userAdmin) .where('users.oauthId', '=', oauthId) .where('users.deletedAt', 'is', null) .executeTakeFirst() as Promise; } - getDeletedUsers(): Promise { - return this.db - .selectFrom('users') - .select(columns) - .where('users.deletedAt', 'is not', null) - .execute() as unknown as Promise; + @GenerateSql({ params: [DateTime.now().minus({ years: 1 })] }) + getDeletedAfter(target: DateTime) { + return this.db.selectFrom('users').select(['id']).where('users.deletedAt', '<', target.toJSDate()).execute(); } - getList({ withDeleted }: UserListFilter = {}): Promise { + @GenerateSql( + { name: 'with deleted', params: [{ withDeleted: true }] }, + { name: 'without deleted', params: [{ withDeleted: false }] }, + ) + getList({ withDeleted }: UserListFilter = {}) { return this.db .selectFrom('users') - .select(columns) + .select(columns.userAdmin) .select(withMetadata) .$if(!withDeleted, (eb) => eb.where('users.deletedAt', 'is', null)) .orderBy('createdAt', 'desc') - .execute() as unknown as Promise; + .execute() as Promise; } async create(dto: Insertable): Promise { return this.db .insertInto('users') .values(dto) - .returning(columns) + .returning(columns.userAdmin) .executeTakeFirst() as unknown as Promise; } @@ -157,7 +142,7 @@ export class UserRepository { .set(dto) .where('users.id', '=', asUuid(id)) .where('users.deletedAt', 'is', null) - .returning(columns) + .returning(columns.userAdmin) .returning(withMetadata) .executeTakeFirst() as unknown as Promise; } @@ -167,7 +152,7 @@ export class UserRepository { .updateTable('users') .set({ status: UserStatus.ACTIVE, deletedAt: null }) .where('users.id', '=', asUuid(id)) - .returning(columns) + .returning(columns.userAdmin) .returning(withMetadata) .executeTakeFirst() as unknown as Promise; } diff --git a/server/src/services/partner.service.ts b/server/src/services/partner.service.ts index 28ceab470e..3723634948 100644 --- a/server/src/services/partner.service.ts +++ b/server/src/services/partner.service.ts @@ -2,7 +2,7 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { Partner } from 'src/database'; import { AuthDto } from 'src/dtos/auth.dto'; import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto'; -import { mapDatabaseUser } from 'src/dtos/user.dto'; +import { mapUser } from 'src/dtos/user.dto'; import { Permission } from 'src/enum'; import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository'; import { BaseService } from 'src/services/base.service'; @@ -49,7 +49,7 @@ export class PartnerService extends BaseService { private mapPartner(partner: Partner, direction: PartnerDirection): PartnerResponseDto { // this is opposite to return the non-me user of the "partner" - const user = mapDatabaseUser( + const user = mapUser( direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy, ) as PartnerResponseDto; diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index b9fa39a8c2..c2433f13cb 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -6,6 +6,7 @@ import { ImmichFileResponse } from 'src/utils/file'; import { authStub } from 'test/fixtures/auth.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { userStub } from 'test/fixtures/user.stub'; +import { factory } from 'test/small.factory'; import { newTestService, ServiceMocks } from 'test/utils'; const makeDeletedAt = (daysAgo: number) => { @@ -20,7 +21,6 @@ describe(UserService.name, () => { beforeEach(() => { ({ sut, mocks } = newTestService(UserService)); - mocks.user.get.mockImplementation((userId) => Promise.resolve([userStub.admin, userStub.user1].find((user) => user.id === userId) ?? undefined), ); @@ -28,36 +28,40 @@ describe(UserService.name, () => { describe('getAll', () => { it('admin should get all users', async () => { - mocks.user.getList.mockResolvedValue([userStub.admin]); - await expect(sut.search(authStub.admin)).resolves.toEqual([ - expect.objectContaining({ - id: authStub.admin.user.id, - email: authStub.admin.user.email, - }), - ]); + const user = factory.userAdmin(); + const auth = factory.auth(user); + + mocks.user.getList.mockResolvedValue([user]); + + await expect(sut.search(auth)).resolves.toEqual([expect.objectContaining({ id: user.id, email: user.email })]); + expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: false }); }); it('non-admin should get all users when publicUsers enabled', async () => { mocks.user.getList.mockResolvedValue([userStub.user1]); + await expect(sut.search(authStub.user1)).resolves.toEqual([ expect.objectContaining({ id: authStub.user1.user.id, email: authStub.user1.user.email, }), ]); + expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: false }); }); it('non-admin user should only receive itself when publicUsers is disabled', async () => { mocks.user.getList.mockResolvedValue([userStub.user1]); mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.publicUsersDisabled); + await expect(sut.search(authStub.user1)).resolves.toEqual([ expect.objectContaining({ id: authStub.user1.user.id, email: authStub.user1.user.email, }), ]); + expect(mocks.user.getList).not.toHaveBeenCalledWith({ withDeleted: false }); }); }); @@ -65,13 +69,17 @@ describe(UserService.name, () => { describe('get', () => { it('should get a user by id', async () => { mocks.user.get.mockResolvedValue(userStub.admin); + await sut.get(authStub.admin.user.id); + expect(mocks.user.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false }); }); it('should throw an error if a user is not found', async () => { mocks.user.get.mockResolvedValue(void 0); + await expect(sut.get(authStub.admin.user.id)).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.user.get).toHaveBeenCalledWith(authStub.admin.user.id, { withDeleted: false }); }); }); @@ -79,6 +87,7 @@ describe(UserService.name, () => { describe('getMe', () => { it("should get the auth user's info", async () => { const user = authStub.admin.user; + await expect(sut.getMe(authStub.admin)).resolves.toMatchObject({ id: user.id, email: user.email, @@ -89,6 +98,7 @@ describe(UserService.name, () => { describe('createProfileImage', () => { it('should throw an error if the user does not exist', async () => { const file = { path: '/profile/path' } as Express.Multer.File; + mocks.user.get.mockResolvedValue(void 0); mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); @@ -105,20 +115,24 @@ describe(UserService.name, () => { it('should delete the previous profile image', async () => { const file = { path: '/profile/path' } as Express.Multer.File; - mocks.user.get.mockResolvedValue(userStub.profilePath); const files = [userStub.profilePath.profileImagePath]; + + mocks.user.get.mockResolvedValue(userStub.profilePath); mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); await sut.createProfileImage(authStub.admin, file); + expect(mocks.job.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]); }); it('should not delete the profile image if it has not been set', async () => { const file = { path: '/profile/path' } as Express.Multer.File; + mocks.user.get.mockResolvedValue(userStub.admin); mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path }); await sut.createProfileImage(authStub.admin, file); + expect(mocks.job.queue).not.toHaveBeenCalled(); expect(mocks.job.queueAll).not.toHaveBeenCalled(); }); @@ -129,6 +143,7 @@ describe(UserService.name, () => { mocks.user.get.mockResolvedValue(userStub.admin); await expect(sut.deleteProfileImage(authStub.admin)).rejects.toBeInstanceOf(BadRequestException); + expect(mocks.job.queue).not.toHaveBeenCalled(); expect(mocks.job.queueAll).not.toHaveBeenCalled(); }); @@ -138,6 +153,7 @@ describe(UserService.name, () => { const files = [userStub.profilePath.profileImagePath]; await sut.deleteProfileImage(authStub.admin); + expect(mocks.job.queue.mock.calls).toEqual([[{ name: JobName.DELETE_FILES, data: { files } }]]); }); }); @@ -176,53 +192,22 @@ describe(UserService.name, () => { describe('handleQueueUserDelete', () => { it('should skip users not ready for deletion', async () => { - mocks.user.getDeletedUsers.mockResolvedValue([ - {}, - { deletedAt: undefined }, - { deletedAt: null }, - { deletedAt: makeDeletedAt(5) }, - ] as UserEntity[]); + mocks.user.getDeletedAfter.mockResolvedValue([]); await sut.handleUserDeleteCheck(); - expect(mocks.user.getDeletedUsers).toHaveBeenCalled(); - expect(mocks.job.queue).not.toHaveBeenCalled(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([]); - }); - - it('should skip users not ready for deletion - deleteDelay30', async () => { - mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.deleteDelay30); - mocks.user.getDeletedUsers.mockResolvedValue([ - {}, - { deletedAt: undefined }, - { deletedAt: null }, - { deletedAt: makeDeletedAt(15) }, - ] as UserEntity[]); - - await sut.handleUserDeleteCheck(); - - expect(mocks.user.getDeletedUsers).toHaveBeenCalled(); + expect(mocks.user.getDeletedAfter).toHaveBeenCalled(); expect(mocks.job.queue).not.toHaveBeenCalled(); expect(mocks.job.queueAll).toHaveBeenCalledWith([]); }); it('should queue user ready for deletion', async () => { - const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) }; - mocks.user.getDeletedUsers.mockResolvedValue([user] as UserEntity[]); + const user = factory.user(); + mocks.user.getDeletedAfter.mockResolvedValue([{ id: user.id }]); await sut.handleUserDeleteCheck(); - expect(mocks.user.getDeletedUsers).toHaveBeenCalled(); - expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.USER_DELETION, data: { id: user.id } }]); - }); - - it('should queue user ready for deletion - deleteDelay30', async () => { - const user = { id: 'deleted-user', deletedAt: makeDeletedAt(31) }; - mocks.user.getDeletedUsers.mockResolvedValue([user] as UserEntity[]); - - await sut.handleUserDeleteCheck(); - - expect(mocks.user.getDeletedUsers).toHaveBeenCalled(); + expect(mocks.user.getDeletedAfter).toHaveBeenCalled(); expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.USER_DELETION, data: { id: user.id } }]); }); }); @@ -230,6 +215,7 @@ describe(UserService.name, () => { describe('handleUserDelete', () => { it('should skip users not ready for deletion', async () => { const user = { id: 'user-1', deletedAt: makeDeletedAt(5) } as UserEntity; + mocks.user.get.mockResolvedValue(user); await sut.handleUserDelete({ id: user.id }); @@ -240,12 +226,12 @@ describe(UserService.name, () => { it('should delete the user and associated assets', async () => { const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10) } as UserEntity; + const options = { force: true, recursive: true }; + mocks.user.get.mockResolvedValue(user); await sut.handleUserDelete({ id: user.id }); - const options = { force: true, recursive: true }; - expect(mocks.storage.unlinkDir).toHaveBeenCalledWith('upload/library/deleted-user', options); expect(mocks.storage.unlinkDir).toHaveBeenCalledWith('upload/upload/deleted-user', options); expect(mocks.storage.unlinkDir).toHaveBeenCalledWith('upload/profile/deleted-user', options); @@ -257,6 +243,7 @@ describe(UserService.name, () => { it('should delete the library path for a storage label', async () => { const user = { id: 'deleted-user', deletedAt: makeDeletedAt(10), storageLabel: 'admin' } as UserEntity; + mocks.user.get.mockResolvedValue(user); await sut.handleUserDelete({ id: user.id }); @@ -269,9 +256,10 @@ describe(UserService.name, () => { describe('setLicense', () => { it('should save client license if valid', async () => { + const license = { licenseKey: 'IMCL-license-key', activationKey: 'activation-key' }; + mocks.user.upsertMetadata.mockResolvedValue(); - const license = { licenseKey: 'IMCL-license-key', activationKey: 'activation-key' }; await sut.setLicense(authStub.user1, license); expect(mocks.user.upsertMetadata).toHaveBeenCalledWith(authStub.user1.user.id, { @@ -281,9 +269,10 @@ describe(UserService.name, () => { }); it('should save server license as client if valid', async () => { + const license = { licenseKey: 'IMSV-license-key', activationKey: 'activation-key' }; + mocks.user.upsertMetadata.mockResolvedValue(); - const license = { licenseKey: 'IMSV-license-key', activationKey: 'activation-key' }; await sut.setLicense(authStub.user1, license); expect(mocks.user.upsertMetadata).toHaveBeenCalledWith(authStub.user1.user.id, { @@ -293,11 +282,13 @@ describe(UserService.name, () => { }); it('should not save license if invalid', async () => { - mocks.user.upsertMetadata.mockResolvedValue(); - const license = { licenseKey: 'license-key', activationKey: 'activation-key' }; const call = sut.setLicense(authStub.admin, license); + + mocks.user.upsertMetadata.mockResolvedValue(); + await expect(call).rejects.toThrowError('Invalid license key'); + expect(mocks.user.upsertMetadata).not.toHaveBeenCalled(); }); }); @@ -307,6 +298,7 @@ describe(UserService.name, () => { mocks.user.upsertMetadata.mockResolvedValue(); await sut.deleteLicense(authStub.admin); + expect(mocks.user.upsertMetadata).not.toHaveBeenCalled(); }); }); @@ -314,6 +306,7 @@ describe(UserService.name, () => { describe('handleUserSyncUsage', () => { it('should sync usage', async () => { await sut.handleUserSyncUsage(); + expect(mocks.user.syncUsage).toHaveBeenCalledTimes(1); }); }); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index f7d6018207..ef06b6f4b1 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -188,15 +188,9 @@ export class UserService extends BaseService { @OnJob({ name: JobName.USER_DELETE_CHECK, queue: QueueName.BACKGROUND_TASK }) async handleUserDeleteCheck(): Promise { - const users = await this.userRepository.getDeletedUsers(); const config = await this.getConfig({ withCache: false }); - await this.jobRepository.queueAll( - users.flatMap((user) => - this.isReadyForDeletion(user, config.user.deleteDelay) - ? [{ name: JobName.USER_DELETION, data: { id: user.id } }] - : [], - ), - ); + const users = await this.userRepository.getDeletedAfter(DateTime.now().minus({ days: config.user.deleteDelay })); + await this.jobRepository.queueAll(users.map((user) => ({ name: JobName.USER_DELETION, data: { id: user.id } }))); return JobStatus.SUCCESS; } diff --git a/server/test/factory.ts b/server/test/factory.ts index 520119fc3e..69160aa8a4 100644 --- a/server/test/factory.ts +++ b/server/test/factory.ts @@ -29,6 +29,7 @@ import { SharedLinkRepository } from 'src/repositories/shared-link.repository'; import { StackRepository } from 'src/repositories/stack.repository'; import { StorageRepository } from 'src/repositories/storage.repository'; import { SyncRepository } from 'src/repositories/sync.repository'; +import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TrashRepository } from 'src/repositories/trash.repository'; import { UserRepository } from 'src/repositories/user.repository'; @@ -205,6 +206,7 @@ export class TestContext { sharedLink: SharedLinkRepository; stack: StackRepository; storage: StorageRepository; + systemMetadata: SystemMetadataRepository; sync: SyncRepository; telemetry: TelemetryRepository; trash: TrashRepository; @@ -241,6 +243,7 @@ export class TestContext { this.stack = new StackRepository(this.db); this.storage = new StorageRepository(logger); this.sync = new SyncRepository(this.db); + this.systemMetadata = new SystemMetadataRepository(this.db); this.telemetry = newTelemetryRepositoryMock() as unknown as TelemetryRepository; this.trash = new TrashRepository(this.db); this.user = new UserRepository(this.db); diff --git a/server/test/fixtures/system-config.stub.ts b/server/test/fixtures/system-config.stub.ts index 89828d781f..355f2cc1a3 100644 --- a/server/test/fixtures/system-config.stub.ts +++ b/server/test/fixtures/system-config.stub.ts @@ -47,11 +47,6 @@ export const systemConfigStub = { defaultStorageQuota: 1, }, }, - deleteDelay30: { - user: { - deleteDelay: 30, - }, - }, libraryWatchEnabled: { library: { scan: { diff --git a/server/test/medium/specs/user.service.spec.ts b/server/test/medium/specs/user.service.spec.ts index 6750dd38d8..fded6ec3b2 100644 --- a/server/test/medium/specs/user.service.spec.ts +++ b/server/test/medium/specs/user.service.spec.ts @@ -1,15 +1,25 @@ +import { Kysely } from 'kysely'; +import { DateTime } from 'luxon'; +import { DB } from 'src/db'; +import { JobName, JobStatus } from 'src/enum'; import { UserService } from 'src/services/user.service'; import { TestContext, TestFactory } from 'test/factory'; -import { getKyselyDB, newTestService } from 'test/utils'; +import { getKyselyDB, newTestService, ServiceMocks } from 'test/utils'; + +const setup = async (db: Kysely) => { + const context = await TestContext.from(db).withUser({ isAdmin: true }).create(); + const { sut, mocks } = newTestService(UserService, context); + + return { sut, mocks, context }; +}; describe.concurrent(UserService.name, () => { let sut: UserService; let context: TestContext; + let mocks: ServiceMocks; beforeAll(async () => { - const db = await getKyselyDB(); - context = await TestContext.from(db).withUser({ isAdmin: true }).create(); - ({ sut } = newTestService(UserService, context)); + ({ sut, context, mocks } = await setup(await getKyselyDB())); }); describe('create', () => { @@ -113,4 +123,50 @@ describe.concurrent(UserService.name, () => { expect(getResponse).toEqual(after); }); }); + + describe('handleUserDeleteCheck', () => { + it('should work when there are no deleted users', async () => { + await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([]); + }); + + it('should work when there is a user to delete', async () => { + const { sut, context, mocks } = await setup(await getKyselyDB()); + const user = TestFactory.user({ deletedAt: DateTime.now().minus({ days: 25 }).toJSDate() }); + + await context.createUser(user); + + await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.USER_DELETION, data: { id: user.id } }]); + }); + + it('should skip a recently deleted user', async () => { + const { sut, context, mocks } = await setup(await getKyselyDB()); + const user = TestFactory.user({ deletedAt: DateTime.now().minus({ days: 5 }).toJSDate() }); + + await context.createUser(user); + + await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([]); + }); + + it('should respect a custom user delete delay', async () => { + const db = await getKyselyDB(); + const { sut, context, mocks } = await setup(db); + const user = TestFactory.user({ deletedAt: DateTime.now().minus({ days: 25 }).toJSDate() }); + await context.createUser(user); + + const config = await sut.getConfig({ withCache: false }); + config.user.deleteDelay = 30; + + await sut.updateConfig(config); + + await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS); + + expect(mocks.job.queueAll).toHaveBeenCalledWith([]); + }); + }); }); diff --git a/server/test/small.factory.ts b/server/test/small.factory.ts index 1faedf311a..5b228d3afb 100644 --- a/server/test/small.factory.ts +++ b/server/test/small.factory.ts @@ -1,8 +1,8 @@ import { randomUUID } from 'node:crypto'; -import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User } from 'src/database'; +import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User, UserAdmin } from 'src/database'; import { AuthDto } from 'src/dtos/auth.dto'; import { OnThisDayData } from 'src/entities/memory.entity'; -import { AssetStatus, AssetType, MemoryType, Permission } from 'src/enum'; +import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum'; import { ActivityItem, MemoryItem } from 'src/types'; export const newUuid = () => randomUUID() as string; @@ -85,6 +85,26 @@ const userFactory = (user: Partial = {}) => ({ ...user, }); +const userAdminFactory = (user: Partial = {}) => ({ + id: newUuid(), + name: 'Test User', + email: 'test@immich.cloud', + profileImagePath: '', + profileChangedAt: newDate(), + storageLabel: null, + shouldChangePassword: false, + isAdmin: false, + createdAt: newDate(), + updatedAt: newDate(), + deletedAt: null, + oauthId: '', + quotaSizeInBytes: null, + quotaUsageInBytes: 0, + status: UserStatus.ACTIVE, + metadata: [], + ...user, +}); + const assetFactory = (asset: Partial = {}) => ({ id: newUuid(), createdAt: newDate(), @@ -198,5 +218,6 @@ export const factory = { session: sessionFactory, stack: stackFactory, user: userFactory, + userAdmin: userAdminFactory, versionHistory: versionHistoryFactory, }; From 2b37caba03915d9151559ea96c5990c4c26f7263 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Mon, 17 Mar 2025 17:08:19 -0400 Subject: [PATCH 11/18] feat(ml): rocm (#16613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ml): introduce support of onnxruntime-rocm for AMD GPU * try mutex for algo cache use OrtMutex * bump versions, run on mich use 3.12 use 1.19.2 * acquire lock before any changes can be made guard algo benchmark results mark mutex as mutable re-add /bin/sh (?) use 3.10 use 6.1.2 * use composite cache key 1.19.2 fix variable name fix variable reference aaaaaaaaaaaaaaaaaaaa * bump deps * disable algo caching * fix gha * try ubuntu runner * actually fix the gha * update patch * skip mimalloc preload for rocm * increase build threads * increase timeout for rocm * Revert "increase timeout for rocm" This reverts commit 2c4452f5d132198ed381a7b262b4a5cab5114b5f. * attempt migraphx * set migraphx_home * Revert "set migraphx_home" This reverts commit c121d3e48754b3bce100636f8d666deec58a44b7. * Revert "attempt migraphx" This reverts commit 521f9fb72dbe506dc6cb8faeb6494817d87265c6. * migraphx, take two * bump rocm * allow cpu * try only targeting migraphx * skip tests * migraph ❌ * known issues * target gfx900 and gfx1102 * mention `HSA_USE_SVM` * update lock * set device id for rocm --------- Co-authored-by: Mehdi GHESH --- .github/workflows/docker.yml | 62 ++++-- docker/docker-compose.dev.yml | 4 +- docker/docker-compose.prod.yml | 4 +- docker/docker-compose.yml | 4 +- docker/hwaccel.ml.yml | 7 + .../docs/features/ml-hardware-acceleration.md | 11 +- docs/docs/guides/remote-machine-learning.md | 4 +- machine-learning/Dockerfile | 46 ++++- machine-learning/README.md | 2 +- machine-learning/app/models/constants.py | 7 +- machine-learning/app/sessions/ort.py | 2 +- machine-learning/app/test_main.py | 31 ++- .../0001-disable-rocm-conv-algo-caching.patch | 179 ++++++++++++++++++ .../patches/0002-target-gfx900-gfx1102.patch | 13 ++ machine-learning/pyproject.toml | 1 + machine-learning/start.sh | 11 +- machine-learning/uv.lock | 2 +- 17 files changed, 340 insertions(+), 50 deletions(-) create mode 100644 machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch create mode 100644 machine-learning/patches/0002-target-gfx900-gfx1102.patch diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4007b07e2e..e7cf85392b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -49,23 +49,38 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - suffix: ["", "-cuda", "-openvino", "-armnn","-rknn"] + suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Re-tag image - run: | - REGISTRY_NAME="ghcr.io" - REPOSITORY=${{ github.repository_owner }}/immich-machine-learning - TAG_OLD=main${{ 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 }} - 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 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Re-tag image + run: | + REGISTRY_NAME="ghcr.io" + REPOSITORY=${{ github.repository_owner }}/immich-machine-learning + TAG_OLD=main${{ 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 }} + 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 + - name: Login to GitHub Container Registry + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Re-tag image + run: | + REGISTRY_NAME="ghcr.io" + REPOSITORY=${{ github.repository_owner }}/immich-machine-learning + TAG_OLD=main${{ 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 }} + 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 retag_server: name: Re-Tag Server @@ -74,7 +89,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - suffix: [""] + suffix: [''] steps: - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 @@ -120,6 +135,11 @@ jobs: device: cuda suffix: -cuda + - platform: linux/amd64 + runner: mich + device: rocm + suffix: -rocm + - platform: linux/amd64 runner: ubuntu-latest device: openvino @@ -129,7 +149,7 @@ jobs: runner: ubuntu-24.04-arm device: armnn suffix: -armnn - + - platform: linux/arm64 runner: ubuntu-24.04-arm device: rknn @@ -220,6 +240,8 @@ jobs: - device: cpu - device: cuda suffix: -cuda + - device: rocm + suffix: -rocm - device: openvino suffix: -openvino - device: armnn @@ -257,7 +279,7 @@ jobs: id: meta uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 env: - DOCKER_METADATA_PR_HEAD_SHA: "true" + DOCKER_METADATA_PR_HEAD_SHA: 'true' with: flavor: | # Disable latest tag @@ -411,7 +433,7 @@ jobs: id: meta uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5 env: - DOCKER_METADATA_PR_HEAD_SHA: "true" + DOCKER_METADATA_PR_HEAD_SHA: 'true' with: flavor: | # Disable latest tag diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 78254c7662..322c8ae8bc 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -95,12 +95,12 @@ services: image: immich-machine-learning-dev:latest # extends: # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference build: context: ../machine-learning dockerfile: Dockerfile args: - - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference + - DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference ports: - 3003:3003 volumes: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index adb00dfbed..c0fcb079ad 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -38,12 +38,12 @@ services: image: immich-machine-learning:latest # extends: # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference build: context: ../machine-learning dockerfile: Dockerfile args: - - DEVICE=cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference + - DEVICE=cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference ports: - 3003:3003 volumes: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6be3189b41..55db2a7cc5 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -33,12 +33,12 @@ services: immich-machine-learning: container_name: immich_machine_learning - # For hardware acceleration, add one of -[armnn, cuda, openvino, rknn] to the image tag. + # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag. # Example tag: ${IMMICH_VERSION:-release}-cuda 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 # file: hwaccel.ml.yml - # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable + # service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable volumes: - model-cache:/cache env_file: diff --git a/docker/hwaccel.ml.yml b/docker/hwaccel.ml.yml index c7035047c7..111202d022 100644 --- a/docker/hwaccel.ml.yml +++ b/docker/hwaccel.ml.yml @@ -33,6 +33,13 @@ services: capabilities: - gpu + rocm: + group_add: + - video + devices: + - /dev/dri:/dev/dri + - /dev/kfd:/dev/kfd + openvino: device_cgroup_rules: - 'c 189:* rmw' diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md index 31d9803c8f..7d001ca6e5 100644 --- a/docs/docs/features/ml-hardware-acceleration.md +++ b/docs/docs/features/ml-hardware-acceleration.md @@ -11,6 +11,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele - ARM NN (Mali) - 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) - RKNN (Rockchip) @@ -44,6 +45,12 @@ 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). - 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=`. 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 - Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM. @@ -64,12 +71,12 @@ You do not need to redo any machine learning jobs after enabling hardware accele 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. -3. Still in `immich-machine-learning`, add one of -[armnn, cuda, openvino] to the `image` section's tag at the end of the line. +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. 4. Redeploy the `immich-machine-learning` container with these updated settings. ### 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 and `intel_gpu_top` for Intel. +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 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. diff --git a/docs/docs/guides/remote-machine-learning.md b/docs/docs/guides/remote-machine-learning.md index 1abf7d4e54..d9b644f106 100644 --- a/docs/docs/guides/remote-machine-learning.md +++ b/docs/docs/guides/remote-machine-learning.md @@ -23,12 +23,12 @@ name: immich_remote_ml services: immich-machine-learning: container_name: immich_machine_learning - # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag. + # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino] to the image tag. # Example tag: ${IMMICH_VERSION:-release}-cuda image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} # extends: # file: hwaccel.ml.yml - # service: # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable + # service: # set to one of [armnn, cuda, rocm, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable volumes: - model-cache:/cache restart: always diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index 4129ffcb8f..a790999dc7 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -17,6 +17,34 @@ RUN mkdir /opt/armnn && \ 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 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 ARG DEVICE @@ -32,6 +60,9 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --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 +RUN if [ "$DEVICE" = "rocm" ]; then \ + uv pip install /opt/onnxruntime_rocm-*.whl; \ + fi FROM python:3.11-slim-bookworm@sha256:614c8691ab74150465ec9123378cd4dde7a6e57be9e558c3108df40664667a4c AS prod-cpu @@ -39,10 +70,10 @@ FROM prod-cpu AS prod-openvino RUN apt-get update && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ - 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 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/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/libigdgmm12_22.4.1_amd64.deb && \ + 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 -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 -nv 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 && \ dpkg -i *.deb && \ rm *.deb && \ apt-get remove wget -yqq && \ @@ -59,6 +90,8 @@ 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/libpython3.11.so /usr/local/lib/libpython3.11.so +FROM rocm/dev-ubuntu-22.04:6.3.4-complete AS prod-rocm + FROM prod-cpu AS prod-armnn ENV LD_LIBRARY_PATH=/opt/armnn @@ -81,13 +114,12 @@ COPY --from=builder-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 + ARG DEVICE RUN apt-get update && \ - apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ]; then echo "libmimalloc2.0"; fi) && \ + apt-get install -y --no-install-recommends tini $(if ! [ "$DEVICE" = "openvino" ] && ! [ "$DEVICE" = "rocm" ]; then echo "libmimalloc2.0"; fi) && \ apt-get autoremove -yqq && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/machine-learning/README.md b/machine-learning/README.md index 4146a6f9de..3d1f09d70b 100644 --- a/machine-learning/README.md +++ b/machine-learning/README.md @@ -7,7 +7,7 @@ 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. -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. +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. 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. diff --git a/machine-learning/app/models/constants.py b/machine-learning/app/models/constants.py index d04ed4d543..79020462a1 100644 --- a/machine-learning/app/models/constants.py +++ b/machine-learning/app/models/constants.py @@ -75,7 +75,12 @@ _INSIGHTFACE_MODELS = { } -SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"] +SUPPORTED_PROVIDERS = [ + "CUDAExecutionProvider", + "ROCMExecutionProvider", + "OpenVINOExecutionProvider", + "CPUExecutionProvider", +] RKNN_SUPPORTED_SOCS = ["rk3566", "rk3568", "rk3576", "rk3588"] RKNN_COREMASK_SUPPORTED_SOCS = ["rk3576", "rk3588"] diff --git a/machine-learning/app/sessions/ort.py b/machine-learning/app/sessions/ort.py index 00c7ad50a9..d15f2d3546 100644 --- a/machine-learning/app/sessions/ort.py +++ b/machine-learning/app/sessions/ort.py @@ -88,7 +88,7 @@ class OrtSession: match provider: case "CPUExecutionProvider": options = {"arena_extend_strategy": "kSameAsRequested"} - case "CUDAExecutionProvider": + case "CUDAExecutionProvider" | "ROCMExecutionProvider": options = {"arena_extend_strategy": "kSameAsRequested", "device_id": settings.device_id} case "OpenVINOExecutionProvider": options = { diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py index 61424ea732..b8eea233d7 100644 --- a/machine-learning/app/test_main.py +++ b/machine-learning/app/test_main.py @@ -180,6 +180,7 @@ class TestOrtSession: OV_EP = ["OpenVINOExecutionProvider", "CPUExecutionProvider"] CUDA_EP_OUT_OF_ORDER = ["CPUExecutionProvider", "CUDAExecutionProvider"] TRT_EP = ["TensorrtExecutionProvider", "CUDAExecutionProvider", "CPUExecutionProvider"] + ROCM_EP = ["ROCMExecutionProvider", "CPUExecutionProvider"] @pytest.mark.providers(CPU_EP) def test_sets_cpu_provider(self, providers: list[str]) -> None: @@ -219,6 +220,12 @@ class TestOrtSession: 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: providers = ["CUDAExecutionProvider"] session = OrtSession("ViT-B-32__openai", providers=providers) @@ -235,19 +242,33 @@ class TestOrtSession: {"arena_extend_strategy": "kSameAsRequested"}, ] - def test_sets_device_id_for_openvino(self) -> None: + def test_sets_provider_options_for_openvino(self) -> None: + model_path = "/cache/ViT-B-32__openai/textual/model.onnx" os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" - session = OrtSession("ViT-B-32__openai", providers=["OpenVINOExecutionProvider"]) + session = OrtSession(model_path, providers=["OpenVINOExecutionProvider"]) - assert session.provider_options[0]["device_type"] == "GPU.1" + assert session.provider_options == [ + { + "device_type": "GPU.1", + "precision": "FP32", + "cache_dir": "/cache/ViT-B-32__openai/textual/openvino", + } + ] - def test_sets_device_id_for_cuda(self) -> None: + def test_sets_provider_options_for_cuda(self) -> None: os.environ["MACHINE_LEARNING_DEVICE_ID"] = "1" session = OrtSession("ViT-B-32__openai", providers=["CUDAExecutionProvider"]) - assert session.provider_options[0]["device_id"] == "1" + assert session.provider_options == [{"arena_extend_strategy": "kSameAsRequested", "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: session = OrtSession( diff --git a/machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch b/machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch new file mode 100644 index 0000000000..6627f67778 --- /dev/null +++ b/machine-learning/patches/0001-disable-rocm-conv-algo-caching.patch @@ -0,0 +1,179 @@ +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::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::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(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 algo_search_workspace = GetTransientScratchBuffer(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::UpdateState(OpKernelContext* context, bool bias_expected) + s_.y_data = reinterpret_cast(s_.Y->MutableData()); + } + } ++ ++ miopenConvAlgoPerf_t perf; ++ int algo_count = 1; ++ const ROCMExecutionProvider* rocm_ep = static_cast(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 algo_search_workspace = GetTransientScratchBuffer(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 + struct MiopenConvState { + // if x/w dims changed, update algo and miopenTensors +@@ -148,9 +145,6 @@ struct MiopenConvState { + decltype(AlgoPerfType().memory) memory; + }; + +- lru_unordered_map cached_benchmark_fwd_results{MAX_CACHED_ALGO_PERF_RESULTS}; +- lru_unordered_map 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::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::DoConvTranspose(OpKernelContext* context, bool dy + } + + y_data = reinterpret_cast(p.Y->MutableData()); +- +- if (!s_.cached_benchmark_bwd_results.contains(x_dims)) { +- IAllocatorUniquePtr algo_search_workspace = GetScratchBuffer(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 algo_search_workspace = GetScratchBuffer(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) { diff --git a/machine-learning/patches/0002-target-gfx900-gfx1102.patch b/machine-learning/patches/0002-target-gfx900-gfx1102.patch new file mode 100644 index 0000000000..fab7a62d8e --- /dev/null +++ b/machine-learning/patches/0002-target-gfx900-gfx1102.patch @@ -0,0 +1,13 @@ +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/*) diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index 140f727de3..a68cd993ba 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -52,6 +52,7 @@ cuda = ["onnxruntime-gpu>=1.17.0,<2"] openvino = ["onnxruntime-openvino>=1.17.1,<1.19.0"] armnn = ["onnxruntime>=1.15.0,<2"] rknn = ["onnxruntime>=1.15.0,<2", "rknn-toolkit-lite2>=2.3.0,<3"] +rocm = [] [tool.uv] compile-bytecode = true diff --git a/machine-learning/start.sh b/machine-learning/start.sh index d2f5b94dc3..859183851c 100755 --- a/machine-learning/start.sh +++ b/machine-learning/start.sh @@ -2,16 +2,19 @@ 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 - export LD_PRELOAD="$lib_path" - export LD_BIND_NOW=1 : "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}" else : "${MACHINE_LEARNING_WORKER_TIMEOUT:=300}" 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_PORT:=3003}" : "${MACHINE_LEARNING_WORKERS:=1}" diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock index 32ac09c7c6..5d7bc31b45 100644 --- a/machine-learning/uv.lock +++ b/machine-learning/uv.lock @@ -1180,7 +1180,7 @@ requires-dist = [ { name = "tokenizers", specifier = ">=0.15.0,<1.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.22.0,<1.0" }, ] -provides-extras = ["cpu", "cuda", "openvino", "armnn", "rknn"] +provides-extras = ["cpu", "cuda", "openvino", "armnn", "rknn", "rocm"] [package.metadata.requires-dev] dev = [ From 6c2985df267e548f1a834f8ccd4b4f5018046088 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 12:15:07 +0100 Subject: [PATCH 12/18] chore(deps): update dependency @types/node to ^22.13.10 (#16944) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/package-lock.json | 4 ++-- cli/package.json | 2 +- e2e/package-lock.json | 6 +++--- e2e/package.json | 2 +- open-api/typescript-sdk/package-lock.json | 2 +- open-api/typescript-sdk/package.json | 2 +- server/package-lock.json | 2 +- server/package.json | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index 3b16a3bf18..8fecb2f10c 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -27,7 +27,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitest/coverage-v8": "^3.0.0", @@ -62,7 +62,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "typescript": "^5.3.3" } }, diff --git a/cli/package.json b/cli/package.json index 334bcc0b0c..2267c92bfa 100644 --- a/cli/package.json +++ b/cli/package.json @@ -21,7 +21,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitest/coverage-v8": "^3.0.0", diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 2288a2b23b..a070cb36d0 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -15,7 +15,7 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", @@ -67,7 +67,7 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@typescript-eslint/eslint-plugin": "^8.15.0", "@typescript-eslint/parser": "^8.15.0", "@vitest/coverage-v8": "^3.0.0", @@ -102,7 +102,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "typescript": "^5.3.3" } }, diff --git a/e2e/package.json b/e2e/package.json index e7dacf3c26..0e50caa3f0 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -25,7 +25,7 @@ "@immich/sdk": "file:../open-api/typescript-sdk", "@playwright/test": "^1.44.1", "@types/luxon": "^3.4.2", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@types/oidc-provider": "^8.5.1", "@types/pg": "^8.11.0", "@types/pngjs": "^6.0.4", diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json index 149c1ed2ce..b502acc544 100644 --- a/open-api/typescript-sdk/package-lock.json +++ b/open-api/typescript-sdk/package-lock.json @@ -12,7 +12,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "typescript": "^5.3.3" } }, diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json index b3fef2a87e..50ad1dd983 100644 --- a/open-api/typescript-sdk/package.json +++ b/open-api/typescript-sdk/package.json @@ -19,7 +19,7 @@ "@oazapfts/runtime": "^1.0.2" }, "devDependencies": { - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "typescript": "^5.3.3" }, "repository": { diff --git a/server/package-lock.json b/server/package-lock.json index fbb997da59..0ef7275af2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -88,7 +88,7 @@ "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", diff --git a/server/package.json b/server/package.json index bc12bc67a0..7f9149f36e 100644 --- a/server/package.json +++ b/server/package.json @@ -114,7 +114,7 @@ "@types/lodash": "^4.14.197", "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@types/nodemailer": "^6.4.14", "@types/picomatch": "^3.0.0", "@types/pngjs": "^6.0.5", From dd263b010c4a799a1cf94137805a6988010da94e Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Tue, 18 Mar 2025 19:02:33 +0530 Subject: [PATCH 13/18] refactor(mobile): use user service methods (#16783) * refactor: user entity * chore: rebase fixes * refactor(mobile): refactor to use user service methods * fix: late init error * fix: lint --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex --- mobile/lib/domain/services/user.service.dart | 6 +- mobile/lib/providers/auth.provider.dart | 55 ++++++++----------- mobile/lib/providers/user.provider.dart | 28 +++------- .../lib/routing/tab_navigation_observer.dart | 19 +------ mobile/lib/services/album.service.dart | 9 ++- mobile/lib/services/api.service.dart | 3 + mobile/lib/services/asset.service.dart | 19 +++---- .../services/backup_verification.service.dart | 9 ++- mobile/lib/services/sync.service.dart | 18 +++--- mobile/lib/services/timeline.service.dart | 23 ++++---- mobile/lib/services/trash.service.dart | 17 +++--- .../widgets/album/album_thumbnail_card.dart | 10 ++-- .../app_bar_dialog/app_bar_profile_info.dart | 7 +-- mobile/lib/widgets/common/immich_app_bar.dart | 5 +- .../domain/services/user_service_test.dart | 44 +++++++++++---- .../modules/shared/sync_service_test.dart | 5 +- mobile/test/services/album.service_test.dart | 3 + 17 files changed, 137 insertions(+), 143 deletions(-) diff --git a/mobile/lib/domain/services/user.service.dart b/mobile/lib/domain/services/user.service.dart index 48a6f5777d..a7eac71291 100644 --- a/mobile/lib/domain/services/user.service.dart +++ b/mobile/lib/domain/services/user.service.dart @@ -44,10 +44,14 @@ class UserService { Future createProfileImage(String name, Uint8List image) async { try { - return await _userApiRepository.createProfileImage( + final path = await _userApiRepository.createProfileImage( name: name, data: image, ); + final updatedUser = getMyUser().copyWith(profileImagePath: path); + await _storeService.put(StoreKey.currentUser, updatedUser); + await _userRepository.update(updatedUser); + return path; } catch (e) { _log.warning("Failed to upload profile image", e); return null; diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index 2a140911b0..9187808984 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -3,11 +3,12 @@ import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/utils/hash.dart'; @@ -18,20 +19,20 @@ final authProvider = StateNotifierProvider((ref) { return AuthNotifier( ref.watch(authServiceProvider), ref.watch(apiServiceProvider), + ref.watch(userServiceProvider), ); }); class AuthNotifier extends StateNotifier { final AuthService _authService; final ApiService _apiService; + final UserService _userService; final _log = Logger("AuthenticationNotifier"); static const Duration _timeoutDuration = Duration(seconds: 7); - AuthNotifier( - this._authService, - this._apiService, - ) : super( + AuthNotifier(this._authService, this._apiService, this._userService) + : super( AuthState( deviceId: "", userId: "", @@ -106,17 +107,21 @@ class AuthNotifier extends StateNotifier { String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid; - UserDto? user = Store.tryGet(StoreKey.currentUser); + UserDto? user = _userService.tryGetMyUser(); - UserAdminResponseDto? userResponse; - UserPreferencesResponseDto? userPreferences; try { - final responses = await Future.wait([ - _apiService.usersApi.getMyUser().timeout(_timeoutDuration), - _apiService.usersApi.getMyPreferences().timeout(_timeoutDuration), - ]); - userResponse = responses[0] as UserAdminResponseDto; - userPreferences = responses[1] as UserPreferencesResponseDto; + final serverUser = + await _userService.refreshMyUser().timeout(_timeoutDuration); + if (serverUser == null) { + _log.severe("Unable to get user information from the server."); + } else { + // If the user information is successfully retrieved, update the store + // Due to the flow of the code, this will always happen on first login + user = serverUser; + await Store.put(StoreKey.deviceId, deviceId); + await Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); + await Store.put(StoreKey.accessToken, accessToken); + } } on ApiException catch (error, stackTrace) { if (error.code == 401) { _log.severe("Unauthorized access, token likely expired. Logging out."); @@ -140,22 +145,6 @@ class AuthNotifier extends StateNotifier { } } - // If the user information is successfully retrieved, update the store - // Due to the flow of the code, this will always happen on first login - if (userResponse == null) { - _log.severe("Unable to get user information from the server."); - } else { - await Store.put(StoreKey.deviceId, deviceId); - await Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); - await Store.put( - StoreKey.currentUser, - UserConverter.fromAdminDto(userResponse, userPreferences), - ); - await Store.put(StoreKey.accessToken, accessToken); - - user = UserConverter.fromAdminDto(userResponse, userPreferences); - } - // If the user is null, the login was not successful // and we don't have a local copy of the user from a prior successful login if (user == null) { @@ -163,13 +152,13 @@ class AuthNotifier extends StateNotifier { } state = state.copyWith( - isAuthenticated: true, + deviceId: deviceId, userId: user.uid, userEmail: user.email, + isAuthenticated: true, name: user.name, - profileImagePath: user.profileImagePath, isAdmin: user.isAdmin, - deviceId: deviceId, + profileImagePath: user.profileImagePath, ); return true; diff --git a/mobile/lib/providers/user.provider.dart b/mobile/lib/providers/user.provider.dart index fb574fa99a..c3623106e8 100644 --- a/mobile/lib/providers/user.provider.dart +++ b/mobile/lib/providers/user.provider.dart @@ -1,34 +1,24 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/services/timeline.service.dart'; class CurrentUserProvider extends StateNotifier { - CurrentUserProvider(this._apiService) : super(null) { - state = Store.tryGet(StoreKey.currentUser); + CurrentUserProvider(this._userService) : super(null) { + state = _userService.tryGetMyUser(); streamSub = - Store.watch(StoreKey.currentUser).listen((user) => state = user); + _userService.watchMyUser().listen((user) => state = user ?? state); } - final ApiService _apiService; + final UserService _userService; late final StreamSubscription streamSub; refresh() async { try { - final user = await _apiService.usersApi.getMyUser(); - final userPreferences = await _apiService.usersApi.getMyPreferences(); - if (user != null) { - await Store.put( - StoreKey.currentUser, - UserConverter.fromAdminDto(user, userPreferences), - ); - } + await _userService.refreshMyUser(); } catch (_) {} } @@ -41,9 +31,7 @@ class CurrentUserProvider extends StateNotifier { final currentUserProvider = StateNotifierProvider((ref) { - return CurrentUserProvider( - ref.watch(apiServiceProvider), - ); + return CurrentUserProvider(ref.watch(userServiceProvider)); }); class TimelineUserIdsProvider extends StateNotifier> { diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart index edbfe6da4c..d95820885e 100644 --- a/mobile/lib/routing/tab_navigation_observer.dart +++ b/mobile/lib/routing/tab_navigation_observer.dart @@ -1,11 +1,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/memory.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -28,19 +25,7 @@ class TabNavigationObserver extends AutoRouterObserver { // Update user info try { - final userResponseDto = - await ref.read(apiServiceProvider).usersApi.getMyUser(); - final userPreferences = - await ref.read(apiServiceProvider).usersApi.getMyPreferences(); - - if (userResponseDto == null) { - return; - } - - await Store.put( - StoreKey.currentUser, - UserConverter.fromAdminDto(userResponseDto, userPreferences), - ); + ref.read(userServiceProvider).refreshMyUser(); ref.read(serverInfoProvider.notifier).getServerVersion(); } catch (e) { debugPrint("Error refreshing user info $e"); diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index d3fe7674d5..1251ef51fe 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -6,12 +6,11 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; import 'package:immich_mobile/interfaces/album.interface.dart'; @@ -21,6 +20,7 @@ import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/album.repository.dart'; import 'package:immich_mobile/repositories/album_api.repository.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart'; @@ -33,6 +33,7 @@ import 'package:logging/logging.dart'; final albumServiceProvider = Provider( (ref) => AlbumService( ref.watch(syncServiceProvider), + ref.watch(userServiceProvider), ref.watch(entityServiceProvider), ref.watch(albumRepositoryProvider), ref.watch(assetRepositoryProvider), @@ -44,6 +45,7 @@ final albumServiceProvider = Provider( class AlbumService { final SyncService _syncService; + final UserService _userService; final EntityService _entityService; final IAlbumRepository _albumRepository; final IAssetRepository _assetRepository; @@ -56,6 +58,7 @@ class AlbumService { AlbumService( this._syncService, + this._userService, this._entityService, this._albumRepository, this._assetRepository, @@ -292,7 +295,7 @@ class AlbumService { Future deleteAlbum(Album album) async { try { - final userId = Store.get(StoreKey.currentUser).id; + final userId = _userService.getMyUser().id; if (album.owner.value?.isarId == userId) { await _albumApiRepository.delete(album.remoteId!); } diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index 0ef68e1c41..1d17b71211 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -35,6 +35,9 @@ class ApiService implements Authentication { late MemoriesApi memoriesApi; ApiService() { + // The below line ensures that the api clients are initialized when the service is instantiated + // This is required to avoid late initialization errors when the clients are access before the endpoint is resolved + setEndpoint(''); final endpoint = Store.tryGet(StoreKey.serverEndpoint); if (endpoint != null && endpoint.isNotEmpty) { setEndpoint(endpoint); diff --git a/mobile/lib/services/asset.service.dart b/mobile/lib/services/asset.service.dart index ff3e908ac3..6eff80ae02 100644 --- a/mobile/lib/services/asset.service.dart +++ b/mobile/lib/services/asset.service.dart @@ -6,9 +6,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/user.interface.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; @@ -19,9 +18,7 @@ import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/user.provider.dart' - hide userServiceProvider; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; @@ -47,7 +44,7 @@ final assetServiceProvider = Provider( ref.watch(syncServiceProvider), ref.watch(backupServiceProvider), ref.watch(albumServiceProvider), - ref.watch(storeServiceProvider), + ref.watch(userServiceProvider), ref.watch(assetMediaRepositoryProvider), ), ); @@ -63,7 +60,7 @@ class AssetService { final SyncService _syncService; final BackupService _backupService; final AlbumService _albumService; - final StoreService _storeService; + final UserService _userService; final IAssetMediaRepository _assetMediaRepository; final log = Logger('AssetService'); @@ -78,7 +75,7 @@ class AssetService { this._syncService, this._backupService, this._albumService, - this._storeService, + this._userService, this._assetMediaRepository, ); @@ -316,7 +313,7 @@ class AssetService { ); await refreshRemoteAssets(); - final owner = _storeService.get(StoreKey.currentUser); + final owner = _userService.getMyUser(); final remoteAssets = await _assetRepository.getAll( ownerId: owner.id, state: AssetState.merged, @@ -522,12 +519,12 @@ class AssetService { } Future> getRecentlyAddedAssets() { - final me = _storeService.get(StoreKey.currentUser); + final me = _userService.getMyUser(); return _assetRepository.getRecentlyAddedAssets(me.id); } Future> getMotionAssets() { - final me = _storeService.get(StoreKey.currentUser); + final me = _userService.getMyUser(); return _assetRepository.getMotionAssets(me.id); } } diff --git a/mobile/lib/services/backup_verification.service.dart b/mobile/lib/services/backup_verification.service.dart index e4d5ab4afd..9aa021a324 100644 --- a/mobile/lib/services/backup_verification.service.dart +++ b/mobile/lib/services/backup_verification.service.dart @@ -8,12 +8,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; @@ -22,11 +24,13 @@ import 'package:immich_mobile/utils/diff.dart'; /// Finds duplicates originating from missing EXIF information class BackupVerificationService { + final UserService _userService; final IFileMediaRepository _fileMediaRepository; final IAssetRepository _assetRepository; final IExifInfoRepository _exifInfoRepository; - BackupVerificationService( + const BackupVerificationService( + this._userService, this._fileMediaRepository, this._assetRepository, this._exifInfoRepository, @@ -34,7 +38,7 @@ class BackupVerificationService { /// Returns at most [limit] assets that were backed up without exif Future> findWronglyBackedUpAssets({int limit = 100}) async { - final owner = Store.get(StoreKey.currentUser).id; + final owner = _userService.getMyUser().id; final List onlyLocal = await _assetRepository.getAll( ownerId: owner, state: AssetState.local, @@ -214,6 +218,7 @@ class BackupVerificationService { final backupVerificationServiceProvider = Provider( (ref) => BackupVerificationService( + ref.watch(userServiceProvider), ref.watch(fileMediaRepositoryProvider), ref.watch(assetRepositoryProvider), ref.watch(exifRepositoryProvider), diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart index a598941f81..24f2fd00d6 100644 --- a/mobile/lib/services/sync.service.dart +++ b/mobile/lib/services/sync.service.dart @@ -5,9 +5,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/interfaces/exif.interface.dart'; import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/domain/interfaces/user_api.repository.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; @@ -20,7 +19,6 @@ import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/partner.interface.dart'; import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/album.repository.dart'; import 'package:immich_mobile/repositories/album_api.repository.dart'; @@ -47,7 +45,7 @@ final syncServiceProvider = Provider( ref.watch(exifRepositoryProvider), ref.watch(partnerRepositoryProvider), ref.watch(userRepositoryProvider), - ref.watch(storeServiceProvider), + ref.watch(userServiceProvider), ref.watch(etagRepositoryProvider), ref.watch(partnerApiRepositoryProvider), ref.watch(userApiRepositoryProvider), @@ -63,8 +61,8 @@ class SyncService { final IAssetRepository _assetRepository; final IExifInfoRepository _exifInfoRepository; final IUserRepository _userRepository; + final UserService _userService; final IPartnerRepository _partnerRepository; - final StoreService _storeService; final IETagRepository _eTagRepository; final IPartnerApiRepository _partnerApiRepository; final IUserApiRepository _userApiRepository; @@ -81,7 +79,7 @@ class SyncService { this._exifInfoRepository, this._partnerRepository, this._userRepository, - this._storeService, + this._userService, this._eTagRepository, this._partnerApiRepository, this._userApiRepository, @@ -210,7 +208,7 @@ class SyncService { DateTime since, ) getChangedAssets, ) async { - final currentUser = _storeService.get(StoreKey.currentUser); + final currentUser = _userService.getMyUser(); final DateTime? since = (await _eTagRepository.get(currentUser.id))?.time?.toUtc(); if (since == null) return null; @@ -261,7 +259,7 @@ class SyncService { Future> _getAllAccessibleUsers() async { final sharedWith = (await _partnerRepository.getSharedWith()).toSet(); - sharedWith.add(_storeService.get(StoreKey.currentUser)); + sharedWith.add(_userService.getMyUser()); return sharedWith.toList(); } @@ -455,7 +453,7 @@ class SyncService { } if (album.shared || dto.shared) { - final userId = (_storeService.get(StoreKey.currentUser)).id; + final userId = (_userService.getMyUser()).id; final foreign = await _assetRepository.getByAlbum(album, notOwnedBy: [userId]); existing.addAll(foreign); @@ -591,7 +589,7 @@ class SyncService { // general case, e.g. some assets have been deleted or there are excluded albums on iOS final inDb = await _assetRepository.getByAlbum( dbAlbum, - ownerId: (_storeService.get(StoreKey.currentUser)).id, + ownerId: (_userService.getMyUser()).id, sortBy: AssetSort.checksum, ); diff --git a/mobile/lib/services/timeline.service.dart b/mobile/lib/services/timeline.service.dart index 03042e266b..dd2252602d 100644 --- a/mobile/lib/services/timeline.service.dart +++ b/mobile/lib/services/timeline.service.dart @@ -1,11 +1,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/timeline.interface.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/timeline.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; @@ -14,28 +13,28 @@ final timelineServiceProvider = Provider((ref) { return TimelineService( ref.watch(timelineRepositoryProvider), ref.watch(appSettingsServiceProvider), - ref.watch(storeServiceProvider), + ref.watch(userServiceProvider), ); }); class TimelineService { final ITimelineRepository _timelineRepository; final AppSettingsService _appSettingsService; - final StoreService _storeService; + final UserService _userService; const TimelineService( this._timelineRepository, this._appSettingsService, - this._storeService, + this._userService, ); Future> getTimelineUserIds() async { - final me = _storeService.get(StoreKey.currentUser); + final me = _userService.getMyUser(); return _timelineRepository.getTimelineUserIds(me.id); } Stream> watchTimelineUserIds() async* { - final me = _storeService.get(StoreKey.currentUser); + final me = _userService.getMyUser(); yield* _timelineRepository.watchTimelineUsers(me.id); } @@ -51,13 +50,13 @@ class TimelineService { } Stream watchArchiveTimeline() async* { - final user = _storeService.get(StoreKey.currentUser); + final user = _userService.getMyUser(); yield* _timelineRepository.watchArchiveTimeline(user.id); } Stream watchFavoriteTimeline() async* { - final user = _storeService.get(StoreKey.currentUser); + final user = _userService.getMyUser(); yield* _timelineRepository.watchFavoriteTimeline(user.id); } @@ -70,7 +69,7 @@ class TimelineService { } Stream watchTrashTimeline() async* { - final user = _storeService.get(StoreKey.currentUser); + final user = _userService.getMyUser(); yield* _timelineRepository.watchTrashTimeline(user.id); } @@ -97,7 +96,7 @@ class TimelineService { } Stream watchAssetSelectionTimeline() async* { - final user = _storeService.get(StoreKey.currentUser); + final user = _userService.getMyUser(); yield* _timelineRepository.watchAssetSelectionTimeline(user.id); } diff --git a/mobile/lib/services/trash.service.dart b/mobile/lib/services/trash.service.dart index 338f063fd3..478ad65db0 100644 --- a/mobile/lib/services/trash.service.dart +++ b/mobile/lib/services/trash.service.dart @@ -1,10 +1,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:openapi/api.dart'; @@ -13,19 +12,19 @@ final trashServiceProvider = Provider((ref) { return TrashService( ref.watch(apiServiceProvider), ref.watch(assetRepositoryProvider), - ref.watch(storeServiceProvider), + ref.watch(userServiceProvider), ); }); class TrashService { final ApiService _apiService; final IAssetRepository _assetRepository; - final StoreService _storeService; + final UserService _userService; - TrashService( + const TrashService( this._apiService, this._assetRepository, - this._storeService, + this._userService, ); Future restoreAssets(Iterable assetList) async { @@ -43,7 +42,7 @@ class TrashService { } Future emptyTrash() async { - final user = _storeService.get(StoreKey.currentUser); + final user = _userService.getMyUser(); await _apiService.trashApi.emptyTrash(); @@ -74,7 +73,7 @@ class TrashService { } Future restoreTrash() async { - final user = _storeService.get(StoreKey.currentUser); + final user = _userService.getMyUser(); await _apiService.trashApi.restoreTrash(); diff --git a/mobile/lib/widgets/album/album_thumbnail_card.dart b/mobile/lib/widgets/album/album_thumbnail_card.dart index ec984d1017..089544a9f1 100644 --- a/mobile/lib/widgets/album/album_thumbnail_card.dart +++ b/mobile/lib/widgets/album/album_thumbnail_card.dart @@ -1,13 +1,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/album.entity.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; -class AlbumThumbnailCard extends StatelessWidget { +class AlbumThumbnailCard extends ConsumerWidget { final Function()? onTap; /// Whether or not to show the owner of the album (or "Owned") @@ -26,7 +26,7 @@ class AlbumThumbnailCard extends StatelessWidget { final Album album; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return LayoutBuilder( builder: (context, constraints) { var cardSize = constraints.maxWidth; @@ -58,7 +58,7 @@ class AlbumThumbnailCard extends StatelessWidget { // Add the owner name to the subtitle String? owner; if (showOwner) { - if (album.ownerId == Store.get(StoreKey.currentUser).uid) { + if (album.ownerId == ref.read(currentUserProvider)?.uid) { owner = 'album_thumbnail_owned'.tr(); } else if (album.ownerName != null) { owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]); diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart index 4d4376b71d..93cdcd0e6f 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; @@ -21,7 +19,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { final authState = ref.watch(authProvider); final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status; - final user = Store.tryGet(StoreKey.currentUser); + final user = ref.watch(currentUserProvider); buildUserProfileImage() { if (user == null) { @@ -67,9 +65,6 @@ class AppBarProfileInfoBox extends HookConsumerWidget { profileImagePath, ); if (user != null) { - final updatedUser = - user.copyWith(profileImagePath: profileImagePath); - await Store.put(StoreKey.currentUser, updatedUser); ref.read(currentUserProvider.notifier).refresh(); } } diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index 7c1e0c1c4e..c776f7aa53 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -3,14 +3,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/immich_logo_provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_dialog.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @@ -30,7 +29,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { backupState.backgroundBackup || backupState.autoBackup; final ServerInfo serverInfoState = ref.watch(serverInfoProvider); final immichLogo = ref.watch(immichLogoProvider); - final user = Store.tryGet(StoreKey.currentUser); + final user = ref.watch(currentUserProvider); final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; diff --git a/mobile/test/domain/services/user_service_test.dart b/mobile/test/domain/services/user_service_test.dart index 512f5cb4a2..52344cc9a7 100644 --- a/mobile/test/domain/services/user_service_test.dart +++ b/mobile/test/domain/services/user_service_test.dart @@ -27,12 +27,16 @@ void main() { userApiRepository: mockUserApiRepo, storeService: mockStoreService, ); + + registerFallbackValue(UserStub.admin); + when(() => mockStoreService.get(StoreKey.currentUser)) + .thenReturn(UserStub.admin); + when(() => mockStoreService.tryGet(StoreKey.currentUser)) + .thenReturn(UserStub.admin); }); group('getMyUser', () { test('should return user from store', () { - when(() => mockStoreService.get(StoreKey.currentUser)) - .thenReturn(UserStub.admin); final result = sut.getMyUser(); expect(result, UserStub.admin); }); @@ -47,8 +51,6 @@ void main() { group('tryGetMyUser', () { test('should return user from store', () { - when(() => mockStoreService.tryGet(StoreKey.currentUser)) - .thenReturn(UserStub.admin); final result = sut.tryGetMyUser(); expect(result, UserStub.admin); }); @@ -107,26 +109,48 @@ void main() { group('createProfileImage', () { test('should return profile image path', () async { + const profileImagePath = 'profile.jpg'; + final updatedUser = + UserStub.admin.copyWith(profileImagePath: profileImagePath); + when( () => mockUserApiRepo.createProfileImage( - name: 'profile.jpg', + name: profileImagePath, data: Uint8List(0), ), - ).thenAnswer((_) async => 'profile.jpg'); + ).thenAnswer((_) async => profileImagePath); + when(() => mockStoreService.put(StoreKey.currentUser, updatedUser)) + .thenAnswer((_) async => true); + when(() => mockUserRepo.update(updatedUser)) + .thenAnswer((_) async => UserStub.admin); - final result = await sut.createProfileImage('profile.jpg', Uint8List(0)); - expect(result, 'profile.jpg'); + final result = + await sut.createProfileImage(profileImagePath, Uint8List(0)); + + verify(() => mockStoreService.put(StoreKey.currentUser, updatedUser)) + .called(1); + verify(() => mockUserRepo.update(updatedUser)).called(1); + expect(result, profileImagePath); }); test('should return null if profile image creation fails', () async { + const profileImagePath = 'profile.jpg'; + final updatedUser = + UserStub.admin.copyWith(profileImagePath: profileImagePath); + when( () => mockUserApiRepo.createProfileImage( - name: 'profile.jpg', + name: profileImagePath, data: Uint8List(0), ), ).thenThrow(Exception('Failed to create profile image')); - final result = await sut.createProfileImage('profile.jpg', Uint8List(0)); + final result = + await sut.createProfileImage(profileImagePath, Uint8List(0)); + verifyNever( + () => mockStoreService.put(StoreKey.currentUser, updatedUser), + ); + verifyNever(() => mockUserRepo.update(updatedUser)); expect(result, isNull); }); }); diff --git a/mobile/test/modules/shared/sync_service_test.dart b/mobile/test/modules/shared/sync_service_test.dart index f74c815997..7594c35fff 100644 --- a/mobile/test/modules/shared/sync_service_test.dart +++ b/mobile/test/modules/shared/sync_service_test.dart @@ -15,6 +15,7 @@ import 'package:immich_mobile/interfaces/partner_api.interface.dart'; import 'package:immich_mobile/services/sync.service.dart'; import 'package:mocktail/mocktail.dart'; +import '../../domain/service.mock.dart'; import '../../infrastructure/repository.mock.dart'; import '../../repository.mocks.dart'; import '../../service.mocks.dart'; @@ -62,6 +63,7 @@ void main() { MockPartnerApiRepository(); final MockUserApiRepository userApiRepository = MockUserApiRepository(); final MockPartnerRepository partnerRepository = MockPartnerRepository(); + final MockUserService userService = MockUserService(); final owner = UserDto( uid: "1", @@ -101,11 +103,12 @@ void main() { exifInfoRepository, partnerRepository, userRepository, - StoreService.I, + userService, eTagRepository, partnerApiRepository, userApiRepository, ); + when(() => userService.getMyUser()).thenReturn(owner); when(() => eTagRepository.get(owner.id)) .thenAnswer((_) async => ETag(id: owner.uid, time: DateTime.now())); when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {}); diff --git a/mobile/test/services/album.service_test.dart b/mobile/test/services/album.service_test.dart index 993456ad99..0efd695560 100644 --- a/mobile/test/services/album.service_test.dart +++ b/mobile/test/services/album.service_test.dart @@ -31,6 +31,8 @@ void main() { albumMediaRepository = MockAlbumMediaRepository(); albumApiRepository = MockAlbumApiRepository(); + when(() => userService.getMyUser()).thenReturn(UserStub.user1); + when(() => albumRepository.transaction(any())).thenAnswer( (call) => (call.positionalArguments.first as Function).call(), ); @@ -40,6 +42,7 @@ void main() { sut = AlbumService( syncService, + userService, entityService, albumRepository, assetRepository, From e96ffd43e75b88663af026140d04f9c9fdbbd381 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Tue, 18 Mar 2025 10:14:46 -0400 Subject: [PATCH 14/18] feat: timeline performance (#16446) * Squash - feature complete * remove need to init assetstore * More optimizations. No need to init. Fix tests * lint * add missing selector for e2e * e2e selectors again * Update: fully reactive store, some transitions, bugfixes * merge fallout * Test fallout * safari quirk * security * lint * lint * Bug fixes * lint/format * accidental commit * lock * null check, more throttle * revert long duration * Fix intersection bounds * Fix bugs in intersection calculation * lint, tweak scrubber ui a tiny bit * bugfix - deselecting asset doesnt work * fix not loading bucket, scroll off-by-1 error, jsdoc, naming --- e2e/src/web/specs/shared-link.e2e-spec.ts | 2 +- web/package-lock.json | 10 +- web/package.json | 6 +- web/src/app.css | 25 +- web/src/lib/actions/intersection-observer.ts | 12 +- web/src/lib/actions/resize-observer.ts | 2 +- .../components/album-page/album-viewer.svelte | 8 +- .../asset-viewer/asset-viewer.svelte | 2 +- .../assets/thumbnail/image-thumbnail.svelte | 17 +- .../assets/thumbnail/thumbnail.svelte | 420 ++--- .../assets/thumbnail/video-thumbnail.svelte | 40 +- .../photos-page/asset-date-group.svelte | 305 ++- .../components/photos-page/asset-grid.svelte | 497 ++--- .../photos-page/measure-date-group.svelte | 91 - .../components/photos-page/skeleton.svelte | 32 +- .../shared-components/control-app-bar.svelte | 2 +- .../gallery-viewer/gallery-viewer.svelte | 199 +- .../scrubber/scrubber.svelte | 97 +- .../side-bar/purchase-info.svelte | 13 +- .../shared-components/tree/breadcrumbs.svelte | 2 +- .../duplicates-compare-control.svelte | 2 +- web/src/lib/constants.ts | 27 +- .../lib/stores/asset-interaction.svelte.ts | 11 +- web/src/lib/stores/assets-store.spec.ts | 219 ++- web/src/lib/stores/assets-store.svelte.ts | 1651 ++++++++++------- web/src/lib/stores/timeline.store.ts | 3 - web/src/lib/utils/asset-store-task-manager.ts | 465 ----- web/src/lib/utils/asset-utils.ts | 2 +- web/src/lib/utils/cancellable-task.ts | 135 ++ web/src/lib/utils/idle-callback-support.ts | 22 - web/src/lib/utils/keyed-priority-queue.ts | 50 - web/src/lib/utils/layout-utils.ts | 46 +- web/src/lib/utils/priority-queue.ts | 21 - web/src/lib/utils/timeline-util.ts | 87 +- web/src/lib/utils/tunables.ts | 45 +- .../[[assetId=id]]/+page.svelte | 278 +-- .../[[assetId=id]]/+page.svelte | 29 +- .../[[assetId=id]]/+page.svelte | 9 +- .../[[assetId=id]]/+page.svelte | 14 +- .../[[assetId=id]]/+page.svelte | 2 +- .../[[assetId=id]]/+page.svelte | 8 +- web/src/routes/(user)/people/+page.svelte | 6 +- .../[[assetId=id]]/+page.svelte | 23 +- .../(user)/photos/[[assetId=id]]/+page.svelte | 18 +- .../[[assetId=id]]/+page.svelte | 97 +- .../[[assetId=id]]/+page.svelte | 10 +- .../[[assetId=id]]/+page.svelte | 18 +- web/tsconfig.json | 2 +- 48 files changed, 2318 insertions(+), 2764 deletions(-) delete mode 100644 web/src/lib/components/photos-page/measure-date-group.svelte delete mode 100644 web/src/lib/stores/timeline.store.ts delete mode 100644 web/src/lib/utils/asset-store-task-manager.ts create mode 100644 web/src/lib/utils/cancellable-task.ts delete mode 100644 web/src/lib/utils/idle-callback-support.ts delete mode 100644 web/src/lib/utils/keyed-priority-queue.ts delete mode 100644 web/src/lib/utils/priority-queue.ts diff --git a/e2e/src/web/specs/shared-link.e2e-spec.ts b/e2e/src/web/specs/shared-link.e2e-spec.ts index ed81db4ef5..9313526dab 100644 --- a/e2e/src/web/specs/shared-link.e2e-spec.ts +++ b/e2e/src/web/specs/shared-link.e2e-spec.ts @@ -45,7 +45,7 @@ test.describe('Shared Links', () => { await page.goto(`/share/${sharedLink.key}`); await page.getByRole('heading', { name: 'Test Album' }).waitFor(); await page.locator(`[data-asset-id="${asset.id}"]`).hover(); - await page.waitForSelector('#asset-group-by-date svg'); + await page.waitForSelector('[data-group] svg'); await page.getByRole('checkbox').click(); await page.getByRole('button', { name: 'Download' }).click(); await page.getByText('DOWNLOADING', { exact: true }).waitFor(); diff --git a/web/package-lock.json b/web/package-lock.json index 6b2a0e9bfb..a2da0e1ae9 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -70,8 +70,8 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^5.14.0", - "svelte": "^5.17.4", - "svelte-check": "^4.1.4", + "svelte": "^5.22.6", + "svelte-check": "^4.1.5", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", "typescript": "^5.7.3", @@ -9579,9 +9579,9 @@ } }, "node_modules/vite": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", - "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", + "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/web/package.json b/web/package.json index af45e08659..77da2d24cb 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,7 @@ "build:stats": "BUILD_STATS=true vite build", "package": "svelte-kit package", "preview": "vite preview", - "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'reactive_declaration_non_reactive_property:ignore'", + "check:svelte": "svelte-check --no-tsconfig --fail-on-warnings --compiler-warnings 'reactive_declaration_non_reactive_property:ignore' --ignore src/lib/components/photos-page/asset-grid.svelte", "check:typescript": "tsc --noEmit", "check:watch": "npm run check:svelte -- --watch", "check:code": "npm run format && npm run lint && npm run check:svelte && npm run check:typescript", @@ -56,8 +56,8 @@ "prettier-plugin-sort-json": "^4.1.1", "prettier-plugin-svelte": "^3.3.3", "rollup-plugin-visualizer": "^5.14.0", - "svelte": "^5.17.4", - "svelte-check": "^4.1.4", + "svelte": "^5.22.6", + "svelte-check": "^4.1.5", "tailwindcss": "^3.4.17", "tslib": "^2.6.2", "typescript": "^5.7.3", diff --git a/web/src/app.css b/web/src/app.css index 9bc1695a8f..a256cc9d80 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -135,32 +135,13 @@ input:focus-visible { } /* width */ - .immich-scrollbar::-webkit-scrollbar { - width: 8px; - } - - /* Track */ - .immich-scrollbar::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 16px; - } - - /* Handle */ - .immich-scrollbar::-webkit-scrollbar-thumb { - background: rgba(85, 86, 87, 0.408); - border-radius: 16px; - } - - /* Handle on hover */ - .immich-scrollbar::-webkit-scrollbar-thumb:hover { - background: #4250afad; - border-radius: 16px; + .immich-scrollbar { + scrollbar-width: thin; } /* Hidden scrollbar */ /* width */ - .scrollbar-hidden::-webkit-scrollbar { - display: none; + .scrollbar-hidden { scrollbar-width: none; } diff --git a/web/src/lib/actions/intersection-observer.ts b/web/src/lib/actions/intersection-observer.ts index 3a10074051..74643aa95d 100644 --- a/web/src/lib/actions/intersection-observer.ts +++ b/web/src/lib/actions/intersection-observer.ts @@ -13,6 +13,7 @@ type OnIntersectCallback = (entryOrElement: IntersectionObserverEntry | HTMLElem type OnSeparateCallback = (element: HTMLElement) => unknown; type IntersectionObserverActionProperties = { key?: string; + disabled?: boolean; /** Function to execute when the element leaves the viewport */ onSeparate?: OnSeparateCallback; /** Function to execute when the element enters the viewport */ @@ -83,8 +84,15 @@ const observe = (key: HTMLElement | string, target: HTMLElement, properties: Int }; function configure(key: HTMLElement | string, element: HTMLElement, properties: IntersectionObserverActionProperties) { - elementToConfig.set(key, properties); - observe(key, element, properties); + if (properties.disabled) { + const config = elementToConfig.get(key); + const { observer } = config || {}; + observer?.unobserve(element); + elementToConfig.delete(key); + } else { + elementToConfig.set(key, properties); + observe(key, element, properties); + } } function _intersectionObserver( diff --git a/web/src/lib/actions/resize-observer.ts b/web/src/lib/actions/resize-observer.ts index 9f3adc44b0..4fa35c7d93 100644 --- a/web/src/lib/actions/resize-observer.ts +++ b/web/src/lib/actions/resize-observer.ts @@ -1,4 +1,4 @@ -type OnResizeCallback = (resizeEvent: { target: HTMLElement; width: number; height: number }) => void; +export type OnResizeCallback = (resizeEvent: { target: HTMLElement; width: number; height: number }) => void; let observer: ResizeObserver; let callbacks: WeakMap; diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 8b5b2bff8b..c176402576 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -33,7 +33,10 @@ let { isViewing: showAssetViewer } = assetViewingStore; - const assetStore = new AssetStore({ albumId: album.id, order: album.order }); + const assetStore = new AssetStore(); + $effect(() => void assetStore.updateOptions({ albumId: album.id, order: album.order })); + onDestroy(() => assetStore.destroy()); + const assetInteraction = new AssetInteraction(); dragAndDropFilesStore.subscribe((value) => { @@ -42,9 +45,6 @@ dragAndDropFilesStore.set({ isDragging: false, files: [] }); } }); - onDestroy(() => { - assetStore.destroy(); - }); void; onNext: () => Promise; onPrevious: () => Promise; - onRandom: () => Promise; + onRandom: () => Promise; copyImage?: () => Promise; } diff --git a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte index 9d69bdeeb2..119efe71b5 100644 --- a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte @@ -4,7 +4,6 @@ import Icon from '$lib/components/elements/icon.svelte'; import { TUNABLES } from '$lib/utils/tunables'; import { mdiEyeOffOutline } from '@mdi/js'; - import { onMount } from 'svelte'; import { fade } from 'svelte/transition'; interface Props { @@ -37,7 +36,6 @@ circle = false, hidden = false, border = false, - preload = true, hiddenIconClass = 'text-white', onComplete = undefined, }: Props = $props(); @@ -49,8 +47,6 @@ let loaded = $state(false); let errored = $state(false); - let img = $state(); - const setLoaded = () => { loaded = true; onComplete?.(); @@ -59,11 +55,13 @@ errored = true; onComplete?.(); }; - onMount(() => { - if (img?.complete) { - setLoaded(); + + function mount(elem: HTMLImageElement) { + if (elem.complete) { + loaded = true; + onComplete?.(); } - }); + } let optionalClasses = $derived( [ @@ -82,10 +80,9 @@ {:else} - import { intersectionObserver } from '$lib/actions/intersection-observer'; import Icon from '$lib/components/elements/icon.svelte'; import { ProjectionType } from '$lib/constants'; import { getAssetThumbnailUrl, isSharedLink } from '$lib/utils'; @@ -22,19 +21,11 @@ import ImageThumbnail from './image-thumbnail.svelte'; import VideoThumbnail from './video-thumbnail.svelte'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; - import { AssetStore } from '$lib/stores/assets-store.svelte'; - - import type { DateGroup } from '$lib/utils/timeline-util'; - - import { generateId } from '$lib/utils/generate-id'; - import { onDestroy } from 'svelte'; import { TUNABLES } from '$lib/utils/tunables'; import { thumbhash } from '$lib/actions/thumbhash'; interface Props { asset: AssetResponseDto; - dateGroup?: DateGroup | undefined; - assetStore?: AssetStore | undefined; groupIndex?: number; thumbnailSize?: number | undefined; thumbnailWidth?: number | undefined; @@ -47,29 +38,16 @@ showArchiveIcon?: boolean; showStackedIcon?: boolean; disableMouseOver?: boolean; - intersectionConfig?: { - root?: HTMLElement; - bottom?: string; - top?: string; - left?: string; - priority?: number; - disabled?: boolean; - }; - retrieveElement?: boolean; - onIntersected?: (() => void) | undefined; + onClick?: ((asset: AssetResponseDto) => void) | undefined; - onRetrieveElement?: ((elment: HTMLElement) => void) | undefined; onSelect?: ((asset: AssetResponseDto) => void) | undefined; onMouseEvent?: ((event: { isMouseOver: boolean; selectedGroupIndex: number }) => void) | undefined; handleFocus?: (() => void) | undefined; class?: string; - overrideDisplayForTest?: boolean; } let { - asset, - dateGroup = undefined, - assetStore = undefined, + asset = $bindable(), groupIndex = 0, thumbnailSize = undefined, thumbnailWidth = undefined, @@ -82,42 +60,21 @@ showArchiveIcon = false, showStackedIcon = true, disableMouseOver = false, - intersectionConfig = {}, - retrieveElement = false, - onIntersected = undefined, onClick = undefined, - onRetrieveElement = undefined, onSelect = undefined, onMouseEvent = undefined, handleFocus = undefined, class: className = '', - overrideDisplayForTest = false, }: Props = $props(); let { IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION }, } = TUNABLES; - const componentId = generateId(); - let element: HTMLElement | undefined = $state(); let focussableElement: HTMLElement | undefined = $state(); let mouseOver = $state(false); - let intersecting = $state(false); - let lastRetrievedElement: HTMLElement | undefined = $state(); let loaded = $state(false); - $effect(() => { - if (!retrieveElement) { - lastRetrievedElement = undefined; - } - }); - $effect(() => { - if (retrieveElement && element && lastRetrievedElement !== element) { - lastRetrievedElement = element; - onRetrieveElement?.(element); - } - }); - $effect(() => { if (focussed && document.activeElement !== focussableElement) { focussableElement?.focus(); @@ -126,13 +83,12 @@ let width = $derived(thumbnailSize || thumbnailWidth || 235); let height = $derived(thumbnailSize || thumbnailHeight || 235); - let display = $derived(intersecting); const onIconClickedHandler = (e?: MouseEvent) => { e?.stopPropagation(); e?.preventDefault(); if (!disabled) { - onSelect?.(asset); + onSelect?.($state.snapshot(asset)); } }; @@ -141,7 +97,7 @@ onIconClickedHandler(); return; } - onClick?.(asset); + onClick?.($state.snapshot(asset)); }; const handleClick = (e: MouseEvent) => { if (e.ctrlKey || e.metaKey) { @@ -152,68 +108,18 @@ callClickHandlers(); }; - const _onMouseEnter = () => { + const onMouseEnter = () => { mouseOver = true; onMouseEvent?.({ isMouseOver: true, selectedGroupIndex: groupIndex }); }; - const onMouseEnter = () => { - if (dateGroup && assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ componentId, task: () => _onMouseEnter() }); - } else { - _onMouseEnter(); - } - }; - const onMouseLeave = () => { - if (dateGroup && assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ componentId, task: () => (mouseOver = false) }); - } else { - mouseOver = false; - } + mouseOver = false; }; - - const _onIntersect = () => { - intersecting = true; - onIntersected?.(); - }; - - const onIntersect = () => { - if (intersecting === true) { - return; - } - if (dateGroup && assetStore) { - assetStore.taskManager.intersectedThumbnail(componentId, dateGroup, asset, () => void _onIntersect()); - } else { - void _onIntersect(); - } - }; - - const onSeparate = () => { - if (intersecting === false) { - return; - } - if (dateGroup && assetStore) { - assetStore.taskManager.separatedThumbnail(componentId, dateGroup, asset, () => (intersecting = false)); - } else { - intersecting = false; - } - }; - - onDestroy(() => { - assetStore?.taskManager.removeAllTasksForComponent(componentId); - }); diff --git a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte index a2e30be543..fc3cb2e951 100644 --- a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte @@ -3,12 +3,8 @@ import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; import { mdiAlertCircleOutline, mdiPauseCircleOutline, mdiPlayCircleOutline } from '@mdi/js'; import Icon from '$lib/components/elements/icon.svelte'; - import { AssetStore } from '$lib/stores/assets-store.svelte'; - import { generateId } from '$lib/utils/generate-id'; - import { onDestroy } from 'svelte'; interface Props { - assetStore?: AssetStore | undefined; url: string; durationInSeconds?: number; enablePlayback?: boolean; @@ -20,7 +16,6 @@ } let { - assetStore = undefined, url, durationInSeconds = 0, enablePlayback = $bindable(false), @@ -31,7 +26,6 @@ pauseIcon = mdiPauseCircleOutline, }: Props = $props(); - const componentId = generateId(); let remainingSeconds = $state(durationInSeconds); let loading = $state(true); let error = $state(false); @@ -49,42 +43,16 @@ } }); const onMouseEnter = () => { - if (assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ - componentId, - task: () => { - if (playbackOnIconHover) { - enablePlayback = true; - } - }, - }); - } else { - if (playbackOnIconHover) { - enablePlayback = true; - } + if (playbackOnIconHover) { + enablePlayback = true; } }; const onMouseLeave = () => { - if (assetStore) { - assetStore.taskManager.queueScrollSensitiveTask({ - componentId, - task: () => { - if (playbackOnIconHover) { - enablePlayback = false; - } - }, - }); - } else { - if (playbackOnIconHover) { - enablePlayback = false; - } + if (playbackOnIconHover) { + enablePlayback = false; } }; - - onDestroy(() => { - assetStore?.taskManager.removeAllTasksForComponent(componentId); - });
diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte index 4cc43ef199..e993d3694d 100644 --- a/web/src/lib/components/photos-page/asset-date-group.svelte +++ b/web/src/lib/components/photos-page/asset-date-group.svelte @@ -1,56 +1,51 @@ -
- {#each dateGroups as dateGroup, groupIndex (dateGroup.date)} - {@const display = - dateGroup.intersecting || !!dateGroup.assets.some((asset) => asset.id === assetStore.pendingScrollAssetId)} - {@const geometry = dateGroup.geometry!} +{#each filterIntersecting(bucket.dateGroups) as dateGroup, groupIndex (dateGroup.date)} + {@const absoluteWidth = dateGroup.left} + +
{ + isMouseOverGroup = true; + assetMouseEventHandler(dateGroup.groupTitle, null); + }} + onmouseleave={() => { + isMouseOverGroup = false; + assetMouseEventHandler(dateGroup.groupTitle, null); + }} + > +
{ - assetStore.taskManager.intersectedDateGroup(componentId, dateGroup, () => - assetStore.updateBucketDateGroup(bucket, dateGroup, { intersecting: true }), - ); - }, - onSeparate: () => { - assetStore.taskManager.separatedDateGroup(componentId, dateGroup, () => - assetStore.updateBucketDateGroup(bucket, dateGroup, { intersecting: false }), - ); - }, - top: INTERSECTION_ROOT_TOP, - bottom: INTERSECTION_ROOT_BOTTOM, - root: assetGridElement, - }} - data-display={display} - data-date-group={dateGroup.date} - style:height={dateGroup.height + 'px'} - style:width={geometry.containerWidth + 'px'} - style:overflow="clip" + class="flex z-[100] pt-[calc(1.75rem+1px)] pb-5 h-6 place-items-center text-xs font-medium text-immich-fg bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg md:text-sm" + style:width={dateGroup.width + 'px'} > - {#if !display} - - {/if} - {#if display} - - + {#if !singleSelect && ((hoveredDateGroup === dateGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dateGroup.groupTitle))}
- assetStore.taskManager.queueScrollSensitiveTask({ - componentId, - task: () => { - isMouseOverGroup = true; - assetMouseEventHandler(dateGroup.groupTitle, null); - }, - })} - on:mouseleave={() => { - assetStore.taskManager.queueScrollSensitiveTask({ - componentId, - task: () => { - isMouseOverGroup = false; - assetMouseEventHandler(dateGroup.groupTitle, null); - }, - }); - }} + transition:fly={{ x: -24, duration: 200, opacity: 0.5 }} + class="inline-block px-2 hover:cursor-pointer" + onclick={() => handleSelectGroup(dateGroup.groupTitle, snapshotAssetArray(dateGroup.getAssets()))} + onkeydown={() => handleSelectGroup(dateGroup.groupTitle, snapshotAssetArray(dateGroup.getAssets()))} > - -
- {#if !singleSelect && ((hoveredDateGroup == dateGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dateGroup.groupTitle))} -
handleSelectGroup(dateGroup.groupTitle, dateGroup.assets)} - on:keydown={() => handleSelectGroup(dateGroup.groupTitle, dateGroup.assets)} - > - {#if assetInteraction.selectedGroup.has(dateGroup.groupTitle)} - - {:else} - - {/if} -
- {/if} - - - {dateGroup.groupTitle} - -
- - -
- {#each dateGroup.assets as asset, i (asset.id)} - - {@const top = geometry.getTop(i)} - {@const left = geometry.getLeft(i)} - {@const width = geometry.getWidth(i)} - {@const height = geometry.getHeight(i)} - -
onAssetInGrid?.(asset), - top: `${-TITLE_HEIGHT}px`, - bottom: `${-(viewport.height - TITLE_HEIGHT - 1)}px`, - right: `${-(viewport.width - 1)}px`, - root: assetGridElement, - }} - data-asset-id={asset.id} - class="absolute" - style:top={top + 'px'} - style:left={left + 'px'} - style:width={width + 'px'} - style:height={height + 'px'} - > - onRetrieveElement(dateGroup, asset, element)} - showStackedIcon={withStacked} - {showArchiveIcon} - {asset} - {groupIndex} - onClick={(asset) => onClick(dateGroup.assets, dateGroup.groupTitle, asset)} - onSelect={(asset) => assetSelectHandler(asset, dateGroup.assets, dateGroup.groupTitle)} - onMouseEvent={() => assetMouseEventHandler(dateGroup.groupTitle, asset)} - selected={assetInteraction.selectedAssets.has(asset) || assetStore.albumAssets.has(asset.id)} - handleFocus={() => assetOnFocusHandler(asset)} - focussed={assetInteraction.isFocussedAsset(asset)} - selectionCandidate={assetInteraction.assetSelectionCandidates.has(asset)} - disabled={assetStore.albumAssets.has(asset.id)} - thumbnailWidth={width} - thumbnailHeight={height} - /> -
- {/each} -
+ {#if assetInteraction.selectedGroup.has(dateGroup.groupTitle)} + + {:else} + + {/if}
{/if} + + + {dateGroup.groupTitle} +
- {/each} -
+ + +
+ {#each filterIntersecting(dateGroup.intersetingAssets) as intersectingAsset (intersectingAsset.id)} + {@const position = intersectingAsset.position!} + {@const asset = intersectingAsset.asset!} + + + +
+ onClick(dateGroup.getAssets(), dateGroup.groupTitle, asset)} + onSelect={(asset) => assetSelectHandler(asset, dateGroup.getAssets(), dateGroup.groupTitle)} + onMouseEvent={() => assetMouseEventHandler(dateGroup.groupTitle, $state.snapshot(asset))} + selected={assetInteraction.hasSelectedAsset(asset.id) || dateGroup.bucket.store.albumAssets.has(asset.id)} + selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)} + handleFocus={() => assetOnFocusHandler(asset)} + disabled={dateGroup.bucket.store.albumAssets.has(asset.id)} + thumbnailWidth={position.width} + thumbnailHeight={position.height} + /> +
+ + {/each} +
+
+{/each} diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 1f4f9aca85..970f09793f 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -4,38 +4,26 @@ import type { Action } from '$lib/components/asset-viewer/actions/action'; import { AppRoute, AssetAction } from '$lib/constants'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { AssetBucket, AssetStore, type BucketListener, type ViewportXY } from '$lib/stores/assets-store.svelte'; - import { locale, showDeleteModal } from '$lib/stores/preferences.store'; + import { AssetBucket, AssetStore } from '$lib/stores/assets-store.svelte'; + import { showDeleteModal } from '$lib/stores/preferences.store'; import { isSearchEnabled } from '$lib/stores/search.store'; import { featureFlags } from '$lib/stores/server-config.store'; import { handlePromiseError } from '$lib/utils'; import { deleteAssets } from '$lib/utils/actions'; import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils'; import { navigate } from '$lib/utils/navigation'; - import { - formatGroupTitle, - splitBucketIntoDateGroups, - type ScrubberListener, - type ScrollTargetListener, - } from '$lib/utils/timeline-util'; - import { TUNABLES } from '$lib/utils/tunables'; + import { type ScrubberListener } from '$lib/utils/timeline-util'; import type { AlbumResponseDto, AssetResponseDto, PersonResponseDto } from '@immich/sdk'; - import { throttle } from 'lodash-es'; - import { onDestroy, onMount, type Snippet } from 'svelte'; + import { onMount, type Snippet } from 'svelte'; import Portal from '../shared-components/portal/portal.svelte'; import Scrubber from '../shared-components/scrubber/scrubber.svelte'; import ShowShortcuts from '../shared-components/show-shortcuts.svelte'; import AssetDateGroup from './asset-date-group.svelte'; import DeleteAssetDialog from './delete-asset-dialog.svelte'; - - import { resizeObserver } from '$lib/actions/resize-observer'; - import MeasureDateGroup from '$lib/components/photos-page/measure-date-group.svelte'; - import { intersectionObserver } from '$lib/actions/intersection-observer'; + import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer'; import Skeleton from '$lib/components/photos-page/skeleton.svelte'; import { page } from '$app/stores'; import type { UpdatePayload } from 'vite'; - import { generateId } from '$lib/utils/generate-id'; - import { isTimelineScrolling } from '$lib/stores/timeline.store'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; interface Props { @@ -81,64 +69,41 @@ let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget } = assetViewingStore; - const viewport: ViewportXY = $state({ width: 0, height: 0, x: 0, y: 0 }); - const safeViewport: ViewportXY = $state({ width: 0, height: 0, x: 0, y: 0 }); - - const componentId = generateId(); let element: HTMLElement | undefined = $state(); + let timelineElement: HTMLElement | undefined = $state(); let showShortcuts = $state(false); let showSkeleton = $state(true); - let internalScroll = false; - let navigating = false; - let preMeasure: AssetBucket[] = $state([]); - let lastIntersectedBucketDate: string | undefined; let scrubBucketPercent = $state(0); let scrubBucket: { bucketDate: string | undefined } | undefined = $state(); let scrubOverallPercent: number = $state(0); - let topSectionHeight = $state(0); - let topSectionOffset = $state(0); + // 60 is the bottom spacer element at 60px let bottomSectionHeight = 60; let leadout = $state(false); - const { - ASSET_GRID: { NAVIGATE_ON_ASSET_IN_VIEW }, - BUCKET: { - INTERSECTION_ROOT_TOP: BUCKET_INTERSECTION_ROOT_TOP, - INTERSECTION_ROOT_BOTTOM: BUCKET_INTERSECTION_ROOT_BOTTOM, - }, - THUMBNAIL: { - INTERSECTION_ROOT_TOP: THUMBNAIL_INTERSECTION_ROOT_TOP, - INTERSECTION_ROOT_BOTTOM: THUMBNAIL_INTERSECTION_ROOT_BOTTOM, - }, - } = TUNABLES; - - const isViewportOrigin = () => { - return viewport.height === 0 && viewport.width === 0; - }; - const isEqual = (a: ViewportXY, b: ViewportXY) => { - return a.height == b.height && a.width == b.width && a.x === b.x && a.y === b.y; - }; - - const completeNav = () => { - navigating = false; - if (internalScroll) { - internalScroll = false; - return; - } - + const completeNav = async () => { if ($gridScrollTarget?.at) { - void assetStore.scheduleScrollToAssetId($gridScrollTarget, () => { + try { + const bucket = await assetStore.findBucketForAsset($gridScrollTarget.at); + if (bucket) { + const height = bucket.findAssetAbsolutePosition($gridScrollTarget.at); + if (height) { + element?.scrollTo({ top: height }); + showSkeleton = false; + assetStore.updateIntersections(); + } + } + } catch { element?.scrollTo({ top: 0 }); showSkeleton = false; - }); + } } else { element?.scrollTo({ top: 0 }); showSkeleton = false; } }; - + beforeNavigate(() => (assetStore.suspendTransitions = true)); afterNavigate((nav) => { const { complete, type } = nav; if (type === 'enter') { @@ -147,10 +112,6 @@ complete.then(completeNav, completeNav); }); - beforeNavigate(() => { - navigating = true; - }); - const hmrSupport = () => { // when hmr happens, skeleton is initialized to true by default // normally, loading asset-grid is part of a navigation event, and the completion of @@ -165,7 +126,6 @@ if (assetGridUpdate) { setTimeout(() => { - void assetStore.updateViewport(safeViewport, true); const asset = $page.url.searchParams.get('at'); if (asset) { $gridScrollTarget = { at: asset }; @@ -193,94 +153,60 @@ return () => void 0; }; - const scrollTolastIntersectedBucket = (adjustedBucket: AssetBucket, delta: number) => { - if (lastIntersectedBucketDate) { - const currentIndex = assetStore.buckets.findIndex((b) => b.bucketDate === lastIntersectedBucketDate); - const deltaIndex = assetStore.buckets.indexOf(adjustedBucket); - - if (deltaIndex < currentIndex) { - element?.scrollBy(0, delta); - } - } - }; - - const bucketListener: BucketListener = (event) => { - const { type } = event; - if (type === 'bucket-height') { - const { bucket, delta } = event; - scrollTolastIntersectedBucket(bucket, delta); - } - }; + const updateIsScrolling = () => (assetStore.scrolling = true); + // note: don't throttle, debounch, or otherwise do this function async - it causes flicker + const updateSlidingWindow = () => assetStore.updateSlidingWindow(element?.scrollTop || 0); + const compensateScrollCallback = (delta: number) => element?.scrollBy(0, delta); + const topSectionResizeObserver: OnResizeCallback = ({ height }) => (assetStore.topSectionHeight = height); onMount(() => { - void assetStore - .init({ bucketListener }) - .then(() => (assetStore.connect(), assetStore.updateViewport(safeViewport))); + assetStore.setCompensateScrollCallback(compensateScrollCallback); if (!enableRouting) { showSkeleton = false; } - const dispose = hmrSupport(); + const disposeHmr = hmrSupport(); return () => { - assetStore.disconnect(); - assetStore.destroy(); - dispose(); + assetStore.setCompensateScrollCallback(); + disposeHmr(); }; }); - const _updateViewport = () => void assetStore.updateViewport(safeViewport); - const updateViewport = throttle(_updateViewport, 16); - - function getOffset(bucketDate: string) { - let offset = 0; - for (let a = 0; a < assetStore.buckets.length; a++) { - if (assetStore.buckets[a].bucketDate === bucketDate) { - break; - } - offset += assetStore.buckets[a].bucketHeight; - } - return offset; - } - - const getMaxScrollPercent = () => - (assetStore.timelineHeight + bottomSectionHeight + topSectionHeight - safeViewport.height) / - (assetStore.timelineHeight + bottomSectionHeight + topSectionHeight); + const getMaxScrollPercent = () => { + const totalHeight = assetStore.timelineHeight + bottomSectionHeight + assetStore.topSectionHeight; + return (totalHeight - assetStore.viewportHeight) / totalHeight; + }; const getMaxScroll = () => { if (!element || !timelineElement) { return 0; } - - return topSectionHeight + bottomSectionHeight + (timelineElement.clientHeight - element.clientHeight); + return assetStore.topSectionHeight + bottomSectionHeight + (timelineElement.clientHeight - element.clientHeight); }; const scrollToBucketAndOffset = (bucket: AssetBucket, bucketScrollPercent: number) => { - const topOffset = getOffset(bucket.bucketDate) + topSectionHeight + topSectionOffset; + const topOffset = bucket.top; const maxScrollPercent = getMaxScrollPercent(); const delta = bucket.bucketHeight * bucketScrollPercent; const scrollTop = (topOffset + delta) * maxScrollPercent; - if (!element) { - return; + if (element) { + element.scrollTop = scrollTop; } - - element.scrollTop = scrollTop; }; - const _onScrub: ScrubberListener = ( + // note: don't throttle, debounch, or otherwise make this function async - it causes flicker + const onScrub: ScrubberListener = ( bucketDate: string | undefined, scrollPercent: number, bucketScrollPercent: number, ) => { - if (!bucketDate || assetStore.timelineHeight < safeViewport.height * 2) { + if (!bucketDate || assetStore.timelineHeight < assetStore.viewportHeight * 2) { // edge case - scroll limited due to size of content, must adjust - use use the overall percent instead - const maxScroll = getMaxScroll(); const offset = maxScroll * scrollPercent; - if (!element) { return; } - element.scrollTop = offset; } else { const bucket = assetStore.buckets.find((b) => b.bucketDate === bucketDate); @@ -290,47 +216,16 @@ scrollToBucketAndOffset(bucket, bucketScrollPercent); } }; - const onScrub = throttle(_onScrub, 16, { leading: false, trailing: true }); - - const stopScrub: ScrubberListener = async ( - bucketDate: string | undefined, - _scrollPercent: number, - bucketScrollPercent: number, - ) => { - if (!bucketDate || assetStore.timelineHeight < safeViewport.height * 2) { - // edge case - scroll limited due to size of content, must adjust - use use the overall percent instead - return; - } - const bucket = assetStore.buckets.find((b) => b.bucketDate === bucketDate); - if (!bucket) { - return; - } - if (bucket && !bucket.measured) { - preMeasure.push(bucket); - await assetStore.loadBucket(bucketDate, { preventCancel: true, pending: true }); - await bucket.measuredPromise; - scrollToBucketAndOffset(bucket, bucketScrollPercent); - } - }; - - let scrollObserverTimer: NodeJS.Timeout; - - const _handleTimelineScroll = () => { - $isTimelineScrolling = true; - if (scrollObserverTimer) { - clearTimeout(scrollObserverTimer); - } - scrollObserverTimer = setTimeout(() => { - $isTimelineScrolling = false; - }, 1000); + // note: don't throttle, debounch, or otherwise make this function async - it causes flicker + const handleTimelineScroll = () => { leadout = false; if (!element) { return; } - if (assetStore.timelineHeight < safeViewport.height * 2) { + if (assetStore.timelineHeight < assetStore.viewportHeight * 2) { // edge case - scroll limited due to size of content, must adjust - use the overall percent instead const maxScroll = getMaxScroll(); scrubOverallPercent = Math.min(1, element.scrollTop / maxScroll); @@ -338,8 +233,8 @@ scrubBucket = undefined; scrubBucketPercent = 0; } else { - let top = element?.scrollTop; - if (top < topSectionHeight) { + let top = element.scrollTop; + if (top < assetStore.topSectionHeight) { // in the lead-in area scrubBucket = undefined; scrubBucketPercent = 0; @@ -352,18 +247,24 @@ let maxScrollPercent = getMaxScrollPercent(); let found = false; - // create virtual buckets.... - const vbuckets = [ - { bucketHeight: topSectionHeight, bucketDate: undefined }, - ...assetStore.buckets, - { bucketHeight: bottomSectionHeight, bucketDate: undefined }, - ]; - - for (const bucket of vbuckets) { - let next = top - bucket.bucketHeight * maxScrollPercent; + const bucketsLength = assetStore.buckets.length; + for (let i = -1; i < bucketsLength + 1; i++) { + let bucket: { bucketDate: string | undefined } | undefined; + let bucketHeight = 0; + if (i === -1) { + // lead-in + bucketHeight = assetStore.topSectionHeight; + } else if (i === bucketsLength) { + // lead-out + bucketHeight = bottomSectionHeight; + } else { + bucket = assetStore.buckets[i]; + bucketHeight = assetStore.buckets[i].bucketHeight; + } + let next = top - bucketHeight * maxScrollPercent; if (next < 0) { scrubBucket = bucket; - scrubBucketPercent = top / (bucket.bucketHeight * maxScrollPercent); + scrubBucketPercent = top / (bucketHeight * maxScrollPercent); found = true; break; } @@ -377,34 +278,6 @@ } } }; - const handleTimelineScroll = throttle(_handleTimelineScroll, 16, { leading: false, trailing: true }); - - const _onAssetInGrid = async (asset: AssetResponseDto) => { - if (!enableRouting || navigating || internalScroll) { - return; - } - $gridScrollTarget = { at: asset.id }; - internalScroll = true; - await navigate( - { targetRoute: 'current', assetId: null, assetGridRouteSearchParams: $gridScrollTarget }, - { replaceState: true, forceNavigate: true }, - ); - }; - const onAssetInGrid = NAVIGATE_ON_ASSET_IN_VIEW - ? throttle(_onAssetInGrid, 16, { leading: false, trailing: true }) - : () => void 0; - - const onScrollTarget: ScrollTargetListener = ({ bucket, offset }) => { - element?.scrollTo({ top: offset }); - if (!bucket.measured) { - preMeasure.push(bucket); - } - showSkeleton = false; - assetStore.clearPendingScroll(); - // set intersecting true manually here, to reduce flicker that happens when - // clearing pending scroll, but the intersection observer hadn't yet had time to run - assetStore.updateBucket(bucket.bucketDate, { intersecting: true }); - }; const trashOrDelete = async (force: boolean = false) => { isShowDeleteConfirmation = false; @@ -439,11 +312,9 @@ }; const toggleArchive = async () => { - const ids = await archiveAssets(assetInteraction.selectedAssetsArray, !assetInteraction.isAllArchived); - if (ids) { - assetStore.removeAssets(ids); - deselectAllAssets(); - } + await archiveAssets(assetInteraction.selectedAssetsArray, !assetInteraction.isAllArchived); + assetStore.updateAssets(assetInteraction.selectedAssetsArray); + deselectAllAssets(); }; const focusElement = () => { @@ -458,23 +329,6 @@ } }; - function handleIntersect(bucket: AssetBucket) { - // updateLastIntersectedBucketDate(); - const task = () => { - assetStore.updateBucket(bucket.bucketDate, { intersecting: true }); - void assetStore.loadBucket(bucket.bucketDate); - }; - assetStore.taskManager.intersectedBucket(componentId, bucket, task); - } - - function handleSeparate(bucket: AssetBucket) { - const task = () => { - assetStore.updateBucket(bucket.bucketDate, { intersecting: false }); - bucket.cancel(); - }; - assetStore.taskManager.separatedBucket(componentId, bucket, task); - } - const handlePrevious = async () => { const previousAsset = await assetStore.getPreviousAsset($viewingAsset); @@ -610,7 +464,6 @@ if (!asset) { return; } - onSelect(asset); if (singleSelect && element) { @@ -619,7 +472,7 @@ } const rangeSelection = assetInteraction.assetSelectionCandidates.size > 0; - const deselect = assetInteraction.selectedAssets.has(asset); + const deselect = assetInteraction.hasSelectedAsset(asset.id); // Select/deselect already loaded assets if (deselect) { @@ -637,39 +490,48 @@ assetInteraction.clearAssetSelectionCandidates(); if (assetInteraction.assetSelectionStart && rangeSelection) { - let startBucketIndex = assetStore.getBucketIndexByAssetId(assetInteraction.assetSelectionStart.id); - let endBucketIndex = assetStore.getBucketIndexByAssetId(asset.id); + let startBucket = assetStore.getBucketIndexByAssetId(assetInteraction.assetSelectionStart.id); + let endBucket = assetStore.getBucketIndexByAssetId(asset.id); - if (startBucketIndex === null || endBucketIndex === null) { + if (startBucket === null || endBucket === null) { return; } - if (endBucketIndex < startBucketIndex) { - [startBucketIndex, endBucketIndex] = [endBucketIndex, startBucketIndex]; - } - - // Select/deselect assets in all intermediate buckets - for (let bucketIndex = startBucketIndex + 1; bucketIndex < endBucketIndex; bucketIndex++) { - const bucket = assetStore.buckets[bucketIndex]; - await assetStore.loadBucket(bucket.bucketDate); - for (const asset of bucket.assets) { - if (deselect) { - assetInteraction.removeAssetFromMultiselectGroup(asset); - } else { - handleSelectAsset(asset); + // Select/deselect assets in range (start,end] + let started = false; + for (const bucket of assetStore.buckets) { + if (bucket === startBucket) { + started = true; + } + if (bucket === endBucket) { + break; + } + if (started) { + await assetStore.loadBucket(bucket.bucketDate); + for (const asset of bucket.getAssets()) { + if (deselect) { + assetInteraction.removeAssetFromMultiselectGroup(asset); + } else { + handleSelectAsset(asset); + } } } } // Update date group selection - for (let bucketIndex = startBucketIndex; bucketIndex <= endBucketIndex; bucketIndex++) { - const bucket = assetStore.buckets[bucketIndex]; + started = false; + for (const bucket of assetStore.buckets) { + if (bucket === startBucket) { + started = true; + } + if (bucket === endBucket) { + break; + } // Split bucket into date groups and check each group - const assetsGroupByDate = splitBucketIntoDateGroups(bucket, $locale); - for (const dateGroup of assetsGroupByDate) { - const dateGroupTitle = formatGroupTitle(dateGroup.date); - if (dateGroup.assets.every((a) => assetInteraction.selectedAssets.has(a))) { + for (const dateGroup of bucket.dateGroups) { + const dateGroupTitle = dateGroup.groupTitle; + if (dateGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) { assetInteraction.addGroupToMultiselectGroup(dateGroupTitle); } else { assetInteraction.removeGroupFromMultiselectGroup(dateGroupTitle); @@ -691,14 +553,16 @@ return; } - let start = assetStore.assets.findIndex((a) => a.id === startAsset.id); - let end = assetStore.assets.findIndex((a) => a.id === endAsset.id); + const assets = assetStore.getAssets(); + + let start = assets.findIndex((a) => a.id === startAsset.id); + let end = assets.findIndex((a) => a.id === endAsset.id); if (start > end) { [start, end] = [end, start]; } - assetInteraction.setAssetSelectionCandidates(assetStore.assets.slice(start, end + 1)); + assetInteraction.setAssetSelectionCandidates(assets.slice(start, end + 1)); }; const onSelectStart = (e: Event) => { @@ -710,14 +574,14 @@ const focusNextAsset = async () => { if (assetInteraction.focussedAssetId === null) { const firstAsset = assetStore.getFirstAsset(); - if (firstAsset !== null) { + if (firstAsset) { assetInteraction.focussedAssetId = firstAsset.id; } } else { - const focussedAsset = assetStore.assets.find((asset) => asset.id === assetInteraction.focussedAssetId); + const focussedAsset = assetStore.getAssets().find((asset) => asset.id === assetInteraction.focussedAssetId); if (focussedAsset) { const nextAsset = await assetStore.getNextAsset(focussedAsset); - if (nextAsset !== null) { + if (nextAsset) { assetInteraction.focussedAssetId = nextAsset.id; } } @@ -726,7 +590,7 @@ const focusPreviousAsset = async () => { if (assetInteraction.focussedAssetId !== null) { - const focussedAsset = assetStore.assets.find((asset) => asset.id === assetInteraction.focussedAssetId); + const focussedAsset = assetStore.getAssets().find((asset) => asset.id === assetInteraction.focussedAssetId); if (focussedAsset) { const previousAsset = await assetStore.getPreviousAsset(focussedAsset); if (previousAsset) { @@ -736,11 +600,8 @@ } }; - onDestroy(() => { - assetStore.taskManager.removeAllTasksForComponent(componentId); - }); let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash); - let isEmpty = $derived(assetStore.initialized && assetStore.buckets.length === 0); + let isEmpty = $derived(assetStore.isInitialized && assetStore.buckets.length === 0); let idsSelectedAssets = $derived(assetInteraction.selectedAssetsArray.map(({ id }) => id)); $effect(() => { @@ -749,23 +610,6 @@ } }); - $effect(() => { - if (element && isViewportOrigin()) { - const rect = element.getBoundingClientRect(); - viewport.height = rect.height; - viewport.width = rect.width; - viewport.x = rect.x; - viewport.y = rect.y; - } - if (!isViewportOrigin() && !isEqual(viewport, safeViewport)) { - safeViewport.height = viewport.height; - safeViewport.width = viewport.width; - safeViewport.x = viewport.x; - safeViewport.y = viewport.y; - updateViewport(); - } - }); - let shortcutList = $derived( (() => { if ($isSearchEnabled || $showAssetViewer) { @@ -829,19 +673,34 @@ {#if showShortcuts} (showShortcuts = !showShortcuts)} /> {/if} + {#if assetStore.buckets.length > 0} { + evt.preventDefault(); + let amount = 50; + if (shiftKeyIsDown) { + amount = 500; + } + if (evt.key === 'ArrowUp') { + amount = -amount; + if (shiftKeyIsDown) { + element?.scrollBy({ top: amount, behavior: 'smooth' }); + } + } else if (evt.key === 'ArrowDown') { + element?.scrollBy({ top: amount, behavior: 'smooth' }); + } + }} /> {/if} @@ -850,90 +709,67 @@ id="asset-grid" class="scrollbar-hidden h-full overflow-y-auto outline-none {isEmpty ? 'm-0' : 'ml-4 tall:ml-0 mr-[60px]'}" tabindex="-1" - use:resizeObserver={({ height, width }) => ((viewport.width = width), (viewport.height = height))} + bind:clientHeight={assetStore.viewportHeight} + bind:clientWidth={null, (v) => ((assetStore.viewportWidth = v), updateSlidingWindow())} bind:this={element} - onscroll={() => ((assetStore.lastScrollTime = Date.now()), handleTimelineScroll())} + onscroll={() => (handleTimelineScroll(), updateSlidingWindow(), updateIsScrolling())} > -
((topSectionHeight = height), (topSectionOffset = target.offsetTop))} - class:invisible={showSkeleton} - > - {@render children?.()} - {#if isEmpty} - - {@render empty?.()} - {/if} -
-
+
+ {@render children?.()} + {#if isEmpty} + + {@render empty?.()} + {/if} +
+ {#each assetStore.buckets as bucket (bucket.viewId)} - {@const isPremeasure = preMeasure.includes(bucket)} - {@const display = bucket.intersecting || bucket === assetStore.pendingScrollBucket || isPremeasure} + {@const display = bucket.intersecting} + {@const absoluteHeight = bucket.top} -
handleIntersect(bucket), - onSeparate: () => handleSeparate(bucket), - top: BUCKET_INTERSECTION_ROOT_TOP, - bottom: BUCKET_INTERSECTION_ROOT_BOTTOM, - root: element, - }, - { - key: bucket.viewId + '.bucketintersection', - onIntersect: () => (lastIntersectedBucketDate = bucket.bucketDate), - top: '0px', - bottom: '-' + Math.max(0, safeViewport.height - 1) + 'px', - left: '0px', - right: '0px', - }, - ]} - data-bucket-display={bucket.intersecting} - data-bucket-date={bucket.bucketDate} - style:height={bucket.bucketHeight + 'px'} - > - {#if display && !bucket.measured} - (preMeasure = preMeasure.filter((b) => b !== bucket))} - > - {/if} - - {#if !display || !bucket.measured} - - {/if} - {#if display && bucket.measured} + {#if !bucket.isLoaded} +
+ +
+ {:else if display} +
handleGroupSelect(title, assets)} onSelectAssetCandidates={handleSelectAssetCandidates} onSelectAssets={handleSelectAssets} /> - {/if} -
+
+ {/if} {/each} -
+
@@ -965,6 +801,9 @@ } .bucket { - contain: layout size; + contain: layout size paint; + transform-style: flat; + backface-visibility: hidden; + transform-origin: center center; } diff --git a/web/src/lib/components/photos-page/measure-date-group.svelte b/web/src/lib/components/photos-page/measure-date-group.svelte deleted file mode 100644 index d3dabaa51d..0000000000 --- a/web/src/lib/components/photos-page/measure-date-group.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - - - -
- {#each bucket.dateGroups as dateGroup (dateGroup.date)} -
-
assetStore.updateBucketDateGroup(bucket, dateGroup, { height })}> -
- - {dateGroup.groupTitle} - -
- -
-
-
- {/each} -
diff --git a/web/src/lib/components/photos-page/skeleton.svelte b/web/src/lib/components/photos-page/skeleton.svelte index 601a40cce2..9d1ba69aec 100644 --- a/web/src/lib/components/photos-page/skeleton.svelte +++ b/web/src/lib/components/photos-page/skeleton.svelte @@ -1,30 +1,28 @@ -
- {#if title} -
- {title} -
- {/if} -
+
+
+ {title} +
+
diff --git a/web/src/lib/components/shared-components/control-app-bar.svelte b/web/src/lib/components/shared-components/control-app-bar.svelte index ef0bf3cda7..dbd8ca5a61 100644 --- a/web/src/lib/components/shared-components/control-app-bar.svelte +++ b/web/src/lib/components/shared-components/control-app-bar.svelte @@ -69,7 +69,7 @@
diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index e7f6bfc5f1..1625c92d3c 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -8,13 +8,11 @@ import type { Viewport } from '$lib/stores/assets-store.svelte'; import { showDeleteModal } from '$lib/stores/preferences.store'; import { deleteAssets } from '$lib/utils/actions'; - import { archiveAssets, cancelMultiselect, getAssetRatio } from '$lib/utils/asset-utils'; + import { archiveAssets, cancelMultiselect } from '$lib/utils/asset-utils'; import { featureFlags } from '$lib/stores/server-config.store'; import { handleError } from '$lib/utils/handle-error'; import { navigate } from '$lib/utils/navigation'; - import { calculateWidth } from '$lib/utils/timeline-util'; import { type AssetResponseDto } from '@immich/sdk'; - import justifiedLayout from 'justified-layout'; import { t } from 'svelte-i18n'; import AssetViewer from '../../asset-viewer/asset-viewer.svelte'; import ShowShortcuts from '../show-shortcuts.svelte'; @@ -22,6 +20,8 @@ import { handlePromiseError } from '$lib/utils'; import DeleteAssetDialog from '../../photos-page/delete-asset-dialog.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { debounce } from 'lodash-es'; + import { getJustifiedLayoutFromAssets, type CommonJustifiedLayout } from '$lib/utils/layout-utils'; interface Props { assets: AssetResponseDto[]; @@ -53,11 +53,84 @@ let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore; + let geometry: CommonJustifiedLayout | undefined = $state(); + + $effect(() => { + const _assets = assets; + updateSlidingWindow(); + + geometry = getJustifiedLayoutFromAssets(_assets, { + spacing: 2, + heightTolerance: 0.15, + rowHeight: 235, + rowWidth: Math.floor(viewport.width), + }); + }); + + let assetLayouts = $derived.by(() => { + const assetLayout = []; + let containerHeight = 0; + let containerWidth = 0; + if (geometry) { + containerHeight = geometry.containerHeight; + containerWidth = geometry.containerWidth; + for (const [i, asset] of assets.entries()) { + const layout = { + asset, + top: geometry.getTop(i), + left: geometry.getLeft(i), + width: geometry.getWidth(i), + height: geometry.getHeight(i), + }; + // 54 is the content height of the asset-selection-app-bar + const layoutTopWithOffset = layout.top + 54; + const layoutBottom = layoutTopWithOffset + layout.height; + + const display = layoutTopWithOffset < slidingWindow.bottom && layoutBottom > slidingWindow.top; + assetLayout.push({ ...layout, display }); + } + } + + return { + assetLayout, + containerHeight, + containerWidth, + }; + }); + let showShortcuts = $state(false); let currentViewAssetIndex = 0; let shiftKeyIsDown = $state(false); let lastAssetMouseEvent: AssetResponseDto | null = $state(null); + let slidingWindow = $state({ top: 0, bottom: 0 }); + const updateSlidingWindow = () => { + const v = $state.snapshot(viewport); + const top = document.scrollingElement?.scrollTop || 0; + const bottom = top + v.height; + const w = { + top, + bottom, + }; + slidingWindow = w; + }; + const debouncedOnIntersected = debounce(() => onIntersected?.(), 750, { maxWait: 100, leading: true }); + + let lastIntersectedHeight = 0; + $effect(() => { + // notify we got to (near) the end of scroll + const scrollPercentage = + ((slidingWindow.bottom - viewport.height) / (viewport.height - (document.scrollingElement?.clientHeight || 0))) * + 100; + + if (scrollPercentage > 90) { + const intersectedHeight = geometry?.containerHeight || 0; + if (lastIntersectedHeight !== intersectedHeight) { + debouncedOnIntersected(); + lastIntersectedHeight = intersectedHeight; + } + } + }); const viewAssetHandler = async (asset: AssetResponseDto) => { currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id); setAsset(assets[currentViewAssetIndex]); @@ -75,6 +148,7 @@ const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Shift') { event.preventDefault(); + shiftKeyIsDown = true; } }; @@ -90,7 +164,7 @@ if (!asset) { return; } - const deselect = assetInteraction.selectedAssets.has(asset); + const deselect = assetInteraction.hasSelectedAsset(asset.id); // Select/deselect already loaded assets if (deselect) { @@ -173,7 +247,7 @@ const toggleArchive = async () => { const ids = await archiveAssets(assetInteraction.selectedAssetsArray, !assetInteraction.isAllArchived); if (ids) { - assets.filter((asset) => !ids.includes(asset.id)); + assets = assets.filter((asset) => !ids.includes(asset.id)); deselectAllAssets(); } }; @@ -248,7 +322,7 @@ } }; - const handleRandom = async (): Promise => { + const handleRandom = async (): Promise => { try { let asset: AssetResponseDto | undefined; if (onRandom) { @@ -261,14 +335,14 @@ } if (!asset) { - return null; + return; } await navigateToAsset(asset); return asset; } catch (error) { handleError(error, $t('errors.cannot_navigate_next_asset')); - return null; + return; } }; @@ -335,26 +409,6 @@ let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash); let idsSelectedAssets = $derived(assetInteraction.selectedAssetsArray.map(({ id }) => id)); - let geometry = $derived( - (() => { - const justifiedLayoutResult = justifiedLayout( - assets.map((asset) => getAssetRatio(asset)), - { - boxSpacing: 2, - containerWidth: Math.floor(viewport.width), - containerPadding: 0, - targetRowHeightTolerance: 0.15, - targetRowHeight: 235, - }, - ); - - return { - ...justifiedLayoutResult, - containerWidth: calculateWidth(justifiedLayoutResult.boxes), - }; - })(), - ); - $effect(() => { if (!lastAssetMouseEvent) { assetInteraction.clearAssetSelectionCandidates(); @@ -374,7 +428,13 @@ }); - + updateSlidingWindow()} +/> {#if isShowDeleteConfirmation} 0} -
- {#each assets as asset, i (i)} -
- { - if (assetInteraction.selectionActive) { - handleSelectAssets(asset); - return; - } - void viewAssetHandler(asset); - }} - onSelect={(asset) => handleSelectAssets(asset)} - onMouseEvent={() => assetMouseEventHandler(asset)} - handleFocus={() => assetOnFocusHandler(asset)} - onIntersected={() => (i === Math.max(1, assets.length - 7) ? onIntersected?.() : void 0)} - {showArchiveIcon} - {asset} - selected={assetInteraction.selectedAssets.has(asset)} - focussed={assetInteraction.isFocussedAsset(asset)} - selectionCandidate={assetInteraction.assetSelectionCandidates.has(asset)} - thumbnailWidth={geometry.boxes[i].width} - thumbnailHeight={geometry.boxes[i].height} - /> - {#if showAssetName} -
- {asset.originalFileName} -
- {/if} -
+
+ {#each assetLayouts.assetLayout as layout (layout.asset.id)} + {@const asset = layout.asset} + + {#if layout.display} +
+ { + if (assetInteraction.selectionActive) { + handleSelectAssets(asset); + return; + } + void viewAssetHandler(asset); + }} + onSelect={(asset) => handleSelectAssets(asset)} + onMouseEvent={() => assetMouseEventHandler(asset)} + handleFocus={() => assetOnFocusHandler(asset)} + {showArchiveIcon} + {asset} + selected={assetInteraction.hasSelectedAsset(asset.id)} + selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)} + focussed={assetInteraction.isFocussedAsset(asset)} + thumbnailWidth={layout.width} + thumbnailHeight={layout.height} + /> + {#if showAssetName} +
+ {asset.originalFileName} +
+ {/if} +
+ {/if} {/each}
{/if} diff --git a/web/src/lib/components/shared-components/scrubber/scrubber.svelte b/web/src/lib/components/shared-components/scrubber/scrubber.svelte index 729810d022..d13c12cf6a 100644 --- a/web/src/lib/components/shared-components/scrubber/scrubber.svelte +++ b/web/src/lib/components/shared-components/scrubber/scrubber.svelte @@ -1,10 +1,8 @@
@@ -445,7 +465,14 @@ {#if assetInteraction.isAllUserOwned} - + + assetStore.updateAssetOperation(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + > {/if} @@ -482,6 +509,7 @@ { + assetStore.suspendTransitions = true; viewMode = AlbumPageViewMode.SELECT_ASSETS; oldAt = { at: $gridScrollTarget?.at }; await navigate( @@ -576,127 +604,117 @@ {/if}
- - {#key albumKey} - {#if viewMode === AlbumPageViewMode.SELECT_ASSETS} - - {:else} - 0} - isSelectionMode={viewMode === AlbumPageViewMode.SELECT_THUMBNAIL} - singleSelect={viewMode === AlbumPageViewMode.SELECT_THUMBNAIL} - showArchiveIcon - onSelect={({ id }) => handleUpdateThumbnail(id)} - onEscape={handleEscape} - > - {#if viewMode !== AlbumPageViewMode.SELECT_THUMBNAIL} - -
- (album.albumName = albumName)} - /> + + {#if viewMode !== AlbumPageViewMode.SELECT_ASSETS} + {#if viewMode !== AlbumPageViewMode.SELECT_THUMBNAIL} + +
+ (album.albumName = albumName)} + /> - {#if album.assetCount > 0} - - {/if} + {#if album.assetCount > 0} + + {/if} - - {#if album.albumUsers.length > 0 || (album.hasSharedLink && isOwned)} -
- - {#if album.hasSharedLink && isOwned} - (viewMode = AlbumPageViewMode.LINK_SHARING)} - /> - {/if} + + {#if album.albumUsers.length > 0 || (album.hasSharedLink && isOwned)} +
+ + {#if album.hasSharedLink && isOwned} + (viewMode = AlbumPageViewMode.LINK_SHARING)} + /> + {/if} - - - - - {#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)} - - {/each} - - - {#if albumHasViewers} - (viewMode = AlbumPageViewMode.VIEW_USERS)} - /> - {/if} - - {#if isOwned} - (viewMode = AlbumPageViewMode.SELECT_USERS)} - title={$t('add_more_users')} - /> - {/if} -
- {/if} - - -
- {/if} - - {#if album.assetCount === 0} -
-
-

{$t('add_photos').toUpperCase()}

- -
-
- {/if} -
- {/if} - {#if showActivityStatus} -
- -
+ + {#each album.albumUsers.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)} + + {/each} + + + {#if albumHasViewers} + (viewMode = AlbumPageViewMode.VIEW_USERS)} + /> + {/if} + + {#if isOwned} + (viewMode = AlbumPageViewMode.SELECT_USERS)} + title={$t('add_more_users')} + /> + {/if} +
+ {/if} + + + + {/if} + + {#if album.assetCount === 0} +
+
+

{$t('add_photos').toUpperCase()}

+ +
+
+ {/if} {/if} - {/key} + + + {#if showActivityStatus} +
+ +
+ {/if}
{#if album.albumUsers.length > 0 && album && isShowActivity && $user && !$showAssetViewer} diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte index c8b239218a..86cfefff77 100644 --- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -12,20 +12,23 @@ import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import { AssetAction } from '$lib/constants'; - import { AssetStore } from '$lib/stores/assets-store.svelte'; + import type { PageData } from './$types'; import { mdiPlus, mdiDotsVertical } from '@mdi/js'; import { t } from 'svelte-i18n'; import { onDestroy } from 'svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; interface Props { data: PageData; } let { data }: Props = $props(); + const assetStore = new AssetStore(); + void assetStore.updateOptions({ isArchived: true }); + onDestroy(() => assetStore.destroy()); - const assetStore = new AssetStore({ isArchived: true }); const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -34,10 +37,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - }); {#if assetInteraction.selectionActive} @@ -45,14 +44,28 @@ assets={assetInteraction.selectedAssets} clearSelect={() => assetInteraction.clearMultiselect()} > - assetStore.removeAssets(assetIds)} /> + + assetStore.updateAssetOperation(ids, (asset) => { + asset.isArchived = isArchived; + return { remove: false }; + })} + /> - + + assetStore.updateAssetOperation(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + /> assetStore.removeAssets(assetIds)} /> diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index 02cac3644d..120281b07e 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -29,7 +29,10 @@ let { data }: Props = $props(); - const assetStore = new AssetStore({ isFavorite: true }); + const assetStore = new AssetStore(); + void assetStore.updateOptions({ isFavorite: true }); + onDestroy(() => assetStore.destroy()); + const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -38,10 +41,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - }); diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte index c412aa8ea2..160c236049 100644 --- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -98,7 +98,19 @@ cancelMultiselect(assetInteraction)} /> cancelMultiselect(assetInteraction)} shared /> - + { + if (data.pathAssets && data.pathAssets.length > 0) { + for (const id of ids) { + const asset = data.pathAssets.find((asset) => asset.id === id); + if (asset) { + asset.isFavorite = isFavorite; + } + } + } + }} + /> diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 2239a21cd5..0a71f35ff2 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -123,7 +123,7 @@ async function navigateRandom() { if (viewingAssets.length <= 0) { - return null; + return undefined; } const index = Math.floor(Math.random() * viewingAssets.length); const asset = await setAssetId(viewingAssets[index]); diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 7885086b44..22a0d82cf4 100644 --- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -21,7 +21,9 @@ let { data }: Props = $props(); - const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false, withStacked: true }); + const assetStore = new AssetStore(); + $effect(() => void assetStore.updateOptions({ userId: data.partner.id, isArchived: false, withStacked: true })); + onDestroy(() => assetStore.destroy()); const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -30,10 +32,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - });
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index f5b7d13112..45e18fd398 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -456,10 +456,10 @@ {#if selectHidden} - + {/if} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 6cad217377..c41ae7d85c 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -74,14 +74,9 @@ let numberOfAssets = $state(data.statistics.assets); let { isViewing: showAssetViewer } = assetViewingStore; - const assetStoreOptions = { isArchived: false, personId: data.person.id }; - const assetStore = new AssetStore(assetStoreOptions); - - $effect(() => { - // Check to trigger rebuild the timeline when navigating between people from the info panel - assetStoreOptions.personId = data.person.id; - handlePromiseError(assetStore.updateOptions(assetStoreOptions)); - }); + const assetStore = new AssetStore(); + $effect(() => void assetStore.updateOptions({ isArchived: false, personId: data.person.id })); + onDestroy(() => assetStore.destroy()); const assetInteraction = new AssetInteraction(); @@ -360,9 +355,6 @@ await updateAssetCount(); }; - onDestroy(() => { - assetStore.destroy(); - }); let person = $derived(data.person); let thumbnailData = $derived(getPeopleThumbnailUrl(person)); @@ -418,7 +410,14 @@ - + + assetStore.updateAssetOperation(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + /> assetStore.destroy()); + const assetInteraction = new AssetInteraction(); let selectedAssets = $derived(assetInteraction.selectedAssetsArray); @@ -67,10 +70,6 @@ assetStore.updateAssets([still]); }; - onDestroy(() => { - assetStore.destroy(); - }); - beforeNavigate(() => { isFaceEditMode.value = false; }); @@ -88,7 +87,14 @@ - + + assetStore.updateAssetOperation(ids, (asset) => { + asset.isFavorite = isFavorite; + return { remove: false }; + })} + > {#if assetInteraction.selectedAssets.size > 1 || isAssetStackSelected} diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index a35a30c1c4..c7f62cba0b 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -136,13 +136,17 @@ nextPage = 1; searchResultAssets = []; searchResultAlbums = []; - await loadNextPage(); + await loadNextPage(true); } - const loadNextPage = async () => { + // eslint-disable-next-line svelte/valid-prop-names-in-kit-pages + export const loadNextPage = async (force?: boolean) => { if (!nextPage || searchResultAssets.length >= MAX_ASSET_COUNT) { return; } + if (isLoading && !force) { + return; + } isLoading = true; const searchDto: SearchTerms = { @@ -232,9 +236,6 @@ return tagNames.join(', '); } - // eslint-disable-next-line no-self-assign - const triggerAssetUpdate = () => (searchResultAssets = searchResultAssets); - const onAddToAlbum = (assetIds: string[]) => { if (terms.isNotInAlbum.toString() == 'true') { const assetIdSet = new Set(assetIds); @@ -262,13 +263,23 @@ - + { + for (const id of ids) { + const asset = searchResultAssets.find((asset) => asset.id === id); + if (asset) { + asset.isFavorite = isFavorite; + } + } + }} + /> - + {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} {/if} @@ -281,6 +292,10 @@ {:else}
goto(previousRoute)} backIcon={mdiArrowLeft}> +
@@ -329,45 +344,43 @@ {/if}
-
- {#if searchResultAlbums.length > 0} -
-
{$t('albums').toUpperCase()}
- + {#if searchResultAlbums.length > 0} +
+
{$t('albums').toUpperCase()}
+ -
- {$t('photos_and_videos').toUpperCase()} -
-
- {/if} -
- {#if searchResultAssets.length > 0} - - {:else if !isLoading} -
-
- -

{$t('no_results')}

-

{$t('no_results_description')}

-
-
- {/if} - - {#if isLoading} -
- -
- {/if} +
+ {$t('photos_and_videos').toUpperCase()} +
+ {/if} +
+ {#if searchResultAssets.length > 0} + + {:else if !isLoading} +
+
+ +

{$t('no_results')}

+

{$t('no_results_description')}

+
+
+ {/if} + + {#if isLoading} +
+ +
+ {/if}
diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte index 96bebe34c1..a89da7ad6b 100644 --- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -24,6 +24,7 @@ import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; + import { onDestroy } from 'svelte'; interface Props { data: PageData; @@ -39,8 +40,9 @@ const buildMap = (tags: TagResponseDto[]) => { return Object.fromEntries(tags.map((tag) => [tag.value, tag])); }; - - const assetStore = new AssetStore({}); + const assetStore = new AssetStore(); + $effect(() => void assetStore.updateOptions({ deferInit: !tag, tagId })); + onDestroy(() => assetStore.destroy()); let tags = $state([]); $effect(() => { @@ -52,10 +54,6 @@ let tagId = $derived(tag?.id); let tree = $derived(buildTree(tags.map((tag) => tag.value))); - $effect.pre(() => { - void assetStore.updateOptions({ tagId }); - }); - const handleNavigation = async (tag: string) => { await navigateToView(normalizeTreePath(`${data.path || ''}/${tag}`)); }; diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte index e31929f2c5..209f75a302 100644 --- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -36,8 +36,10 @@ handlePromiseError(goto(AppRoute.PHOTOS)); } - const options = { isTrashed: true }; - const assetStore = new AssetStore(options); + const assetStore = new AssetStore(); + void assetStore.updateOptions({ isTrashed: true }); + onDestroy(() => assetStore.destroy()); + const assetInteraction = new AssetInteraction(); const handleEmptyTrash = async () => { @@ -56,9 +58,6 @@ message: $t('assets_permanently_deleted_count', { values: { count } }), type: NotificationType.Info, }); - - // reset asset grid (TODO fix in asset store that it should reset when it is empty) - await assetStore.updateOptions(options); } catch (error) { handleError(error, $t('errors.unable_to_empty_trash')); } @@ -80,7 +79,10 @@ }); // reset asset grid (TODO fix in asset store that it should reset when it is empty) - await assetStore.updateOptions(options); + // note - this is still a problem, but updateOptions with the same value will not + // do anything, so need to flip it for it to reload/reinit + // await assetStore.updateOptions({ deferInit: true, isTrashed: true }); + // await assetStore.updateOptions({ deferInit: false, isTrashed: true }); } catch (error) { handleError(error, $t('errors.unable_to_restore_trash')); } @@ -92,10 +94,6 @@ return; } }; - - onDestroy(() => { - assetStore.destroy(); - }); {#if assetInteraction.selectionActive} diff --git a/web/tsconfig.json b/web/tsconfig.json index 31aef23e31..c7bc16f52b 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -4,7 +4,7 @@ "checkJs": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "module": "es2020", + "module": "es2022", "moduleResolution": "bundler", "resolveJsonModule": true, "skipLibCheck": true, From 9cf3b88f80b0d9736fc9cb121e0d768da736ce56 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Tue, 18 Mar 2025 21:35:37 +0530 Subject: [PATCH 15/18] refactor(mobile): remove int user id (#16814) * refactor: user entity * chore: rebase fixes * refactor: remove int user Id * refactor: migrate store userId from int to string * refactor: rename uid to id * fix: migration * pr feedback --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> --- .../lib/domain/interfaces/user.interface.dart | 4 +- mobile/lib/domain/models/user.model.dart | 16 +++---- .../lib/extensions/collection_extensions.dart | 7 +-- .../infrastructure/entities/user.entity.dart | 4 +- .../repositories/store.repository.dart | 6 ++- .../repositories/user.repository.dart | 9 +--- .../infrastructure/utils/user.converter.dart | 6 +-- mobile/lib/interfaces/asset.interface.dart | 14 +++--- mobile/lib/interfaces/etag.interface.dart | 2 +- mobile/lib/interfaces/timeline.interface.dart | 19 ++++---- ...additional_shared_user_selection.page.dart | 4 +- .../lib/pages/album/album_options.page.dart | 6 +-- mobile/lib/pages/albums/albums.page.dart | 2 +- mobile/lib/pages/common/activities.page.dart | 2 +- mobile/lib/providers/auth.provider.dart | 2 +- mobile/lib/providers/timeline.provider.dart | 5 ++- mobile/lib/providers/user.provider.dart | 6 +-- mobile/lib/repositories/album.repository.dart | 19 +++----- mobile/lib/repositories/asset.repository.dart | 43 ++++++++++--------- .../repositories/asset_media.repository.dart | 3 +- mobile/lib/repositories/etag.repository.dart | 2 +- .../lib/repositories/timeline.repository.dart | 38 ++++++++-------- mobile/lib/services/album.service.dart | 7 +-- mobile/lib/services/asset.service.dart | 4 +- mobile/lib/services/partner.service.dart | 12 +++--- mobile/lib/services/sync.service.dart | 26 +++++------ mobile/lib/services/timeline.service.dart | 14 +++--- mobile/lib/utils/migration.dart | 22 +++++++++- .../widgets/album/album_thumbnail_card.dart | 2 +- .../asset_viewer/bottom_gallery_bar.dart | 4 +- .../asset_viewer/description_input.dart | 3 +- .../widgets/asset_viewer/gallery_app_bar.dart | 6 ++- mobile/lib/widgets/common/user_avatar.dart | 2 +- .../widgets/common/user_circle_avatar.dart | 2 +- mobile/test/fixtures/user.stub.dart | 6 +-- .../modules/shared/sync_service_test.dart | 4 +- mobile/test/services/album.service_test.dart | 4 +- mobile/test/services/entity.service_test.dart | 2 +- 38 files changed, 182 insertions(+), 157 deletions(-) diff --git a/mobile/lib/domain/interfaces/user.interface.dart b/mobile/lib/domain/interfaces/user.interface.dart index 03f3ebb63e..05176731fd 100644 --- a/mobile/lib/domain/interfaces/user.interface.dart +++ b/mobile/lib/domain/interfaces/user.interface.dart @@ -4,8 +4,6 @@ import 'package:immich_mobile/domain/models/user.model.dart'; abstract interface class IUserRepository implements IDatabaseRepository { Future insert(UserDto user); - Future get(int id); - Future getByUserId(String id); Future> getByUserIds(List ids); @@ -16,7 +14,7 @@ abstract interface class IUserRepository implements IDatabaseRepository { Future update(UserDto user); - Future delete(List ids); + Future delete(List ids); Future deleteAll(); } diff --git a/mobile/lib/domain/models/user.model.dart b/mobile/lib/domain/models/user.model.dart index ceb65f313a..ad241a8c48 100644 --- a/mobile/lib/domain/models/user.model.dart +++ b/mobile/lib/domain/models/user.model.dart @@ -1,7 +1,5 @@ import 'dart:ui'; -import 'package:immich_mobile/utils/hash.dart'; - enum AvatarColor { // do not change this order or reuse indices for other purposes, adding is OK primary, @@ -32,7 +30,7 @@ enum AvatarColor { // TODO: Rename to User once Isar is removed class UserDto { - final String uid; + final String id; final String email; final String name; final bool isAdmin; @@ -50,11 +48,10 @@ class UserDto { final int quotaUsageInBytes; final int quotaSizeInBytes; - int get id => fastHash(uid); bool get hasQuota => quotaSizeInBytes > 0; const UserDto({ - required this.uid, + required this.id, required this.email, required this.name, required this.isAdmin, @@ -73,7 +70,6 @@ class UserDto { String toString() { return '''User: { id: $id, -uid: $uid, email: $email, name: $name, isAdmin: $isAdmin, @@ -90,7 +86,7 @@ quotaSizeInBytes: $quotaSizeInBytes, } UserDto copyWith({ - String? uid, + String? id, String? email, String? name, bool? isAdmin, @@ -105,7 +101,7 @@ quotaSizeInBytes: $quotaSizeInBytes, int? quotaSizeInBytes, }) => UserDto( - uid: uid ?? this.uid, + id: id ?? this.id, email: email ?? this.email, name: name ?? this.name, isAdmin: isAdmin ?? this.isAdmin, @@ -124,7 +120,7 @@ quotaSizeInBytes: $quotaSizeInBytes, bool operator ==(covariant UserDto other) { if (identical(this, other)) return true; - return other.uid == uid && + return other.id == id && other.updatedAt.isAtSameMomentAs(updatedAt) && other.avatarColor == avatarColor && other.email == email && @@ -141,7 +137,7 @@ quotaSizeInBytes: $quotaSizeInBytes, @override int get hashCode => - uid.hashCode ^ + id.hashCode ^ name.hashCode ^ email.hashCode ^ updatedAt.hashCode ^ diff --git a/mobile/lib/extensions/collection_extensions.dart b/mobile/lib/extensions/collection_extensions.dart index e02582588b..95d2f74df8 100644 --- a/mobile/lib/extensions/collection_extensions.dart +++ b/mobile/lib/extensions/collection_extensions.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/utils/hash.dart'; extension ListExtension on List { List uniqueConsecutive({ @@ -62,11 +63,11 @@ extension AssetListExtension on Iterable { void Function()? errorCallback, }) { if (owner == null) return []; - final userId = owner.id; - final bool onlyOwned = every((e) => e.ownerId == userId); + final isarUserId = fastHash(owner.id); + final bool onlyOwned = every((e) => e.ownerId == isarUserId); if (!onlyOwned) { if (errorCallback != null) errorCallback(); - return where((a) => a.ownerId == userId); + return where((a) => a.ownerId == isarUserId); } return this; } diff --git a/mobile/lib/infrastructure/entities/user.entity.dart b/mobile/lib/infrastructure/entities/user.entity.dart index 834559b936..710856d9f7 100644 --- a/mobile/lib/infrastructure/entities/user.entity.dart +++ b/mobile/lib/infrastructure/entities/user.entity.dart @@ -40,7 +40,7 @@ class User { }); static User fromDto(UserDto dto) => User( - id: dto.uid, + id: dto.id, updatedAt: dto.updatedAt, email: dto.email, name: dto.name, @@ -56,7 +56,7 @@ class User { ); UserDto toDto() => UserDto( - uid: id, + id: id, email: email, name: name, isAdmin: isAdmin, diff --git a/mobile/lib/infrastructure/repositories/store.repository.dart b/mobile/lib/infrastructure/repositories/store.repository.dart index 1e5a5335d5..86dfaf4452 100644 --- a/mobile/lib/infrastructure/repositories/store.repository.dart +++ b/mobile/lib/infrastructure/repositories/store.repository.dart @@ -78,7 +78,9 @@ class IsarStoreRepository extends IsarDatabaseRepository const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), - const (UserDto) => await IsarUserRepository(_db).get(entity.intValue!), + const (UserDto) => entity.strValue == null + ? null + : await IsarUserRepository(_db).getByUserId(entity.strValue!), _ => null, } as T?; @@ -89,8 +91,8 @@ class IsarStoreRepository extends IsarDatabaseRepository const (bool) => ((value as bool) ? 1 : 0, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), const (UserDto) => ( - (await IsarUserRepository(_db).update(value as UserDto)).id, null, + (await IsarUserRepository(_db).update(value as UserDto)).id, ), _ => throw UnsupportedError( "Unsupported primitive type: ${key.type} for key: ${key.name}", diff --git a/mobile/lib/infrastructure/repositories/user.repository.dart b/mobile/lib/infrastructure/repositories/user.repository.dart index 2cb8023b2a..f12b95f64b 100644 --- a/mobile/lib/infrastructure/repositories/user.repository.dart +++ b/mobile/lib/infrastructure/repositories/user.repository.dart @@ -11,9 +11,9 @@ class IsarUserRepository extends IsarDatabaseRepository const IsarUserRepository(super.db) : _db = db; @override - Future delete(List ids) async { + Future delete(List ids) async { await transaction(() async { - await _db.users.deleteAll(ids); + await _db.users.deleteAllById(ids); }); } @@ -24,11 +24,6 @@ class IsarUserRepository extends IsarDatabaseRepository }); } - @override - Future get(int id) async { - return (await _db.users.get(id))?.toDto(); - } - @override Future> getAll({SortUserBy? sortBy}) async { return (await _db.users diff --git a/mobile/lib/infrastructure/utils/user.converter.dart b/mobile/lib/infrastructure/utils/user.converter.dart index 11f9ddec33..fcf7ede51c 100644 --- a/mobile/lib/infrastructure/utils/user.converter.dart +++ b/mobile/lib/infrastructure/utils/user.converter.dart @@ -4,7 +4,7 @@ import 'package:openapi/api.dart'; abstract final class UserConverter { /// Base user dto used where the complete user object is not required static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto( - uid: dto.id, + id: dto.id, email: dto.email, name: dto.name, isAdmin: false, @@ -18,7 +18,7 @@ abstract final class UserConverter { UserPreferencesResponseDto? preferenceDto, ]) => UserDto( - uid: adminDto.id, + id: adminDto.id, email: adminDto.email, name: adminDto.name, isAdmin: adminDto.isAdmin, @@ -34,7 +34,7 @@ abstract final class UserConverter { ); static UserDto fromPartnerDto(PartnerResponseDto dto) => UserDto( - uid: dto.id, + id: dto.id, email: dto.email, name: dto.name, isAdmin: false, diff --git a/mobile/lib/interfaces/asset.interface.dart b/mobile/lib/interfaces/asset.interface.dart index 83a020f843..ed524c4f35 100644 --- a/mobile/lib/interfaces/asset.interface.dart +++ b/mobile/lib/interfaces/asset.interface.dart @@ -19,7 +19,7 @@ abstract interface class IAssetRepository implements IDatabaseRepository { ); Future> getAll({ - required int ownerId, + required String ownerId, AssetState? state, AssetSort? sortBy, int? limit, @@ -29,8 +29,8 @@ abstract interface class IAssetRepository implements IDatabaseRepository { Future> getByAlbum( Album album, { - Iterable notOwnedBy = const [], - int? ownerId, + Iterable notOwnedBy = const [], + String? ownerId, AssetState? state, AssetSort? sortBy, }); @@ -45,7 +45,7 @@ abstract interface class IAssetRepository implements IDatabaseRepository { Future> getMatches({ required List assets, - required int ownerId, + required String ownerId, AssetState? state, int limit = 100, }); @@ -64,10 +64,10 @@ abstract interface class IAssetRepository implements IDatabaseRepository { Stream watchAsset(int id, {bool fireImmediately = false}); - Future> getTrashAssets(int userId); + Future> getTrashAssets(String userId); - Future> getRecentlyAddedAssets(int userId); - Future> getMotionAssets(int userId); + Future> getRecentlyAddedAssets(String userId); + Future> getMotionAssets(String userId); } enum AssetSort { checksum, ownerIdChecksum } diff --git a/mobile/lib/interfaces/etag.interface.dart b/mobile/lib/interfaces/etag.interface.dart index 22942b0e34..8b4b5806c9 100644 --- a/mobile/lib/interfaces/etag.interface.dart +++ b/mobile/lib/interfaces/etag.interface.dart @@ -2,7 +2,7 @@ import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/interfaces/database.interface.dart'; abstract interface class IETagRepository implements IDatabaseRepository { - Future get(int id); + Future get(String id); Future getById(String id); diff --git a/mobile/lib/interfaces/timeline.interface.dart b/mobile/lib/interfaces/timeline.interface.dart index d43f87ed5b..bc486a785f 100644 --- a/mobile/lib/interfaces/timeline.interface.dart +++ b/mobile/lib/interfaces/timeline.interface.dart @@ -3,22 +3,25 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; abstract class ITimelineRepository { - Future> getTimelineUserIds(int id); + Future> getTimelineUserIds(String id); - Stream> watchTimelineUsers(int id); + Stream> watchTimelineUsers(String id); - Stream watchArchiveTimeline(int userId); - Stream watchFavoriteTimeline(int userId); - Stream watchTrashTimeline(int userId); + Stream watchArchiveTimeline(String userId); + Stream watchFavoriteTimeline(String userId); + Stream watchTrashTimeline(String userId); Stream watchAlbumTimeline( Album album, GroupAssetsBy groupAssetsBy, ); Stream watchAllVideosTimeline(); - Stream watchHomeTimeline(int userId, GroupAssetsBy groupAssetsBy); + Stream watchHomeTimeline( + String userId, + GroupAssetsBy groupAssetsBy, + ); Stream watchMultiUsersTimeline( - List userIds, + List userIds, GroupAssetsBy groupAssetsBy, ); @@ -27,5 +30,5 @@ abstract class ITimelineRepository { GroupAssetsBy getGroupByOption, ); - Stream watchAssetSelectionTimeline(int userId); + Stream watchAssetSelectionTimeline(String userId); } diff --git a/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart b/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart index 2dc41b396d..194f749a32 100644 --- a/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart +++ b/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart @@ -26,7 +26,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { final sharedUsersList = useState>({}); addNewUsersHandler() { - context.maybePop(sharedUsersList.value.map((e) => e.uid).toList()); + context.maybePop(sharedUsersList.value.map((e) => e.id).toList()); } buildTileIcon(UserDto user) { @@ -151,7 +151,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { onData: (users) { for (var sharedUsers in album.sharedUsers) { users.removeWhere( - (u) => u.uid == sharedUsers.id || u.uid == album.ownerId, + (u) => u.id == sharedUsers.id || u.id == album.ownerId, ); } diff --git a/mobile/lib/pages/album/album_options.page.dart b/mobile/lib/pages/album/album_options.page.dart index a765be50b3..73a03154f6 100644 --- a/mobile/lib/pages/album/album_options.page.dart +++ b/mobile/lib/pages/album/album_options.page.dart @@ -85,7 +85,7 @@ class AlbumOptionsPage extends HookConsumerWidget { void handleUserClick(UserDto user) { var actions = []; - if (user.uid == userId) { + if (user.id == userId) { actions = [ ListTile( leading: const Icon(Icons.exit_to_app_rounded), @@ -170,10 +170,10 @@ class AlbumOptionsPage extends HookConsumerWidget { color: context.colorScheme.onSurfaceSecondary, ), ), - trailing: userId == user.uid || isOwner + trailing: userId == user.id || isOwner ? const Icon(Icons.more_horiz_rounded) : const SizedBox(), - onTap: userId == user.uid || isOwner + onTap: userId == user.id || isOwner ? () => handleUserClick(user) : null, ); diff --git a/mobile/lib/pages/albums/albums.page.dart b/mobile/lib/pages/albums/albums.page.dart index acdfbf385f..e5758c959c 100644 --- a/mobile/lib/pages/albums/albums.page.dart +++ b/mobile/lib/pages/albums/albums.page.dart @@ -33,7 +33,7 @@ class AlbumsPage extends HookConsumerWidget { final searchController = useTextEditingController(); final debounceTimer = useRef(null); final filterMode = useState(QuickFilterMode.all); - final userId = ref.watch(currentUserProvider)?.uid; + final userId = ref.watch(currentUserProvider)?.id; final searchFocusNode = useFocusNode(); toggleViewMode() { diff --git a/mobile/lib/pages/common/activities.page.dart b/mobile/lib/pages/common/activities.page.dart index 84a8622fa5..776ee9977b 100644 --- a/mobile/lib/pages/common/activities.page.dart +++ b/mobile/lib/pages/common/activities.page.dart @@ -72,7 +72,7 @@ class ActivitiesPage extends HookConsumerWidget { final activity = data[index]; final canDelete = activity.user.id == user?.id || - album.ownerId == user?.uid; + album.ownerId == user?.id; return Padding( padding: const EdgeInsets.all(5), diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index 9187808984..3221b80526 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -153,7 +153,7 @@ class AuthNotifier extends StateNotifier { state = state.copyWith( deviceId: deviceId, - userId: user.uid, + userId: user.id, userEmail: user.email, isAuthenticated: true, name: user.name, diff --git a/mobile/lib/providers/timeline.provider.dart b/mobile/lib/providers/timeline.provider.dart index 97d5698c4c..f857d8aa6c 100644 --- a/mobile/lib/providers/timeline.provider.dart +++ b/mobile/lib/providers/timeline.provider.dart @@ -5,7 +5,7 @@ import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/services/timeline.service.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; -final singleUserTimelineProvider = StreamProvider.family( +final singleUserTimelineProvider = StreamProvider.family( (ref, userId) { if (userId == null) { return const Stream.empty(); @@ -18,7 +18,8 @@ final singleUserTimelineProvider = StreamProvider.family( dependencies: [localeProvider], ); -final multiUsersTimelineProvider = StreamProvider.family>( +final multiUsersTimelineProvider = + StreamProvider.family>( (ref, userIds) { ref.watch(localeProvider); final timelineService = ref.watch(timelineServiceProvider); diff --git a/mobile/lib/providers/user.provider.dart b/mobile/lib/providers/user.provider.dart index c3623106e8..99dfba9010 100644 --- a/mobile/lib/providers/user.provider.dart +++ b/mobile/lib/providers/user.provider.dart @@ -34,7 +34,7 @@ final currentUserProvider = return CurrentUserProvider(ref.watch(userServiceProvider)); }); -class TimelineUserIdsProvider extends StateNotifier> { +class TimelineUserIdsProvider extends StateNotifier> { TimelineUserIdsProvider(this._timelineService) : super([]) { _timelineService.getTimelineUserIds().then((users) => state = users); streamSub = _timelineService @@ -42,7 +42,7 @@ class TimelineUserIdsProvider extends StateNotifier> { .listen((users) => state = users); } - late final StreamSubscription> streamSub; + late final StreamSubscription> streamSub; final TimelineService _timelineService; @override @@ -53,6 +53,6 @@ class TimelineUserIdsProvider extends StateNotifier> { } final timelineUsersIdsProvider = - StateNotifierProvider>((ref) { + StateNotifierProvider>((ref) { return TimelineUserIdsProvider(ref.watch(timelineServiceProvider)); }); diff --git a/mobile/lib/repositories/album.repository.dart b/mobile/lib/repositories/album.repository.dart index a6657f7637..8c50c54382 100644 --- a/mobile/lib/repositories/album.repository.dart +++ b/mobile/lib/repositories/album.repository.dart @@ -10,6 +10,7 @@ import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:isar/isar.dart'; final albumRepositoryProvider = @@ -43,14 +44,11 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { if (shared != null) { query = query.sharedEqualTo(shared); } + final isarUserId = fastHash(Store.get(StoreKey.currentUser).id); if (owner == true) { - query = query.owner( - (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id), - ); + query = query.owner((q) => q.isarIdEqualTo(isarUserId)); } else if (owner == false) { - query = query.owner( - (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id), - ); + query = query.owner((q) => q.not().isarIdEqualTo(isarUserId)); } if (remote == true) { query = query.localIdIsNull(); @@ -140,16 +138,13 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository { .filter() .nameContains(searchTerm, caseSensitive: false) .remoteIdIsNotNull(); + final isarUserId = fastHash(Store.get(StoreKey.currentUser).id); switch (filterMode) { case QuickFilterMode.sharedWithMe: - query = query.owner( - (q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).id), - ); + query = query.owner((q) => q.not().isarIdEqualTo(isarUserId)); case QuickFilterMode.myAlbums: - query = query.owner( - (q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).id), - ); + query = query.owner((q) => q.isarIdEqualTo(isarUserId)); case QuickFilterMode.all: break; } diff --git a/mobile/lib/repositories/asset.repository.dart b/mobile/lib/repositories/asset.repository.dart index c27660c352..ac1c768df0 100644 --- a/mobile/lib/repositories/asset.repository.dart +++ b/mobile/lib/repositories/asset.repository.dart @@ -11,6 +11,7 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:isar/isar.dart'; final assetRepositoryProvider = @@ -22,20 +23,21 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository { @override Future> getByAlbum( Album album, { - Iterable notOwnedBy = const [], - int? ownerId, + Iterable notOwnedBy = const [], + String? ownerId, AssetState? state, AssetSort? sortBy, }) { var query = album.assets.filter(); + final isarUserIds = notOwnedBy.map(fastHash).toList(); if (notOwnedBy.length == 1) { - query = query.not().ownerIdEqualTo(notOwnedBy.first); + query = query.not().ownerIdEqualTo(isarUserIds.first); } else if (notOwnedBy.isNotEmpty) { query = - query.not().anyOf(notOwnedBy, (q, int id) => q.ownerIdEqualTo(id)); + query.not().anyOf(isarUserIds, (q, int id) => q.ownerIdEqualTo(id)); } if (ownerId != null) { - query = query.ownerIdEqualTo(ownerId); + query = query.ownerIdEqualTo(fastHash(ownerId)); } if (state != null) { @@ -87,27 +89,28 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository { @override Future> getAll({ - required int ownerId, + required String ownerId, AssetState? state, AssetSort? sortBy, int? limit, }) { final baseQuery = db.assets.where(); + final isarUserIds = fastHash(ownerId); final QueryBuilder filteredQuery = switch (state) { - null => baseQuery.ownerIdEqualToAnyChecksum(ownerId).noOp(), + null => baseQuery.ownerIdEqualToAnyChecksum(isarUserIds).noOp(), AssetState.local => baseQuery .remoteIdIsNull() .filter() .localIdIsNotNull() - .ownerIdEqualTo(ownerId), + .ownerIdEqualTo(isarUserIds), AssetState.remote => baseQuery .localIdIsNull() .filter() .remoteIdIsNotNull() - .ownerIdEqualTo(ownerId), + .ownerIdEqualTo(isarUserIds), AssetState.merged => baseQuery - .ownerIdEqualToAnyChecksum(ownerId) + .ownerIdEqualToAnyChecksum(isarUserIds) .filter() .remoteIdIsNotNull() .localIdIsNotNull(), @@ -132,7 +135,7 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository { @override Future> getMatches({ required List assets, - required int ownerId, + required String ownerId, AssetState? state, int limit = 100, }) { @@ -147,7 +150,7 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository { AssetState.merged => baseQuery.localIdIsNotNull().filter().remoteIdIsNotNull(), }; - return _getMatchesImpl(query, ownerId, assets, limit); + return _getMatchesImpl(query, fastHash(ownerId), assets, limit); } @override @@ -185,10 +188,10 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository { @override Future> getAllByOwnerIdChecksum( - List ids, + List ownerIds, List checksums, ) => - db.assets.getAllByOwnerIdChecksum(ids, checksums); + db.assets.getAllByOwnerIdChecksum(ownerIds, checksums); @override Future> getAllLocal() => @@ -224,30 +227,30 @@ class AssetRepository extends DatabaseRepository implements IAssetRepository { } @override - Future> getTrashAssets(int userId) { + Future> getTrashAssets(String userId) { return db.assets .where() .remoteIdIsNotNull() .filter() - .ownerIdEqualTo(userId) + .ownerIdEqualTo(fastHash(userId)) .isTrashedEqualTo(true) .findAll(); } @override - Future> getRecentlyAddedAssets(int userId) { + Future> getRecentlyAddedAssets(String userId) { return db.assets .where() - .ownerIdEqualToAnyChecksum(userId) + .ownerIdEqualToAnyChecksum(fastHash(userId)) .sortByFileCreatedAtDesc() .findAll(); } @override - Future> getMotionAssets(int userId) { + Future> getMotionAssets(String userId) { return db.assets .where() - .ownerIdEqualToAnyChecksum(userId) + .ownerIdEqualToAnyChecksum(fastHash(userId)) .filter() .livePhotoVideoIdIsNotNull() .findAll(); diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 0149a8d6c6..7df26455cd 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -4,6 +4,7 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:photo_manager/photo_manager.dart' hide AssetType; final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository()); @@ -24,7 +25,7 @@ class AssetMediaRepository implements IAssetMediaRepository { final Asset asset = Asset( checksum: "", localId: local.id, - ownerId: Store.get(StoreKey.currentUser).id, + ownerId: fastHash(Store.get(StoreKey.currentUser).id), fileCreatedAt: local.createDateTime, fileModifiedAt: local.modifiedDateTime, updatedAt: local.modifiedDateTime, diff --git a/mobile/lib/repositories/etag.repository.dart b/mobile/lib/repositories/etag.repository.dart index 93d98de28c..e8e0624a89 100644 --- a/mobile/lib/repositories/etag.repository.dart +++ b/mobile/lib/repositories/etag.repository.dart @@ -15,7 +15,7 @@ class ETagRepository extends DatabaseRepository implements IETagRepository { Future> getAllIds() => db.eTags.where().idProperty().findAll(); @override - Future get(int id) => db.eTags.get(id); + Future get(String id) => db.eTags.getById(id); @override Future upsertAll(List etags) => txn(() => db.eTags.putAll(etags)); diff --git a/mobile/lib/repositories/timeline.repository.dart b/mobile/lib/repositories/timeline.repository.dart index 1b0471059f..319ce3e5b4 100644 --- a/mobile/lib/repositories/timeline.repository.dart +++ b/mobile/lib/repositories/timeline.repository.dart @@ -6,6 +6,7 @@ import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/timeline.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:isar/isar.dart'; @@ -17,32 +18,32 @@ class TimelineRepository extends DatabaseRepository TimelineRepository(super.db); @override - Future> getTimelineUserIds(int id) { + Future> getTimelineUserIds(String id) { return db.users .filter() .inTimelineEqualTo(true) .or() - .isarIdEqualTo(id) - .isarIdProperty() + .idEqualTo(id) + .idProperty() .findAll(); } @override - Stream> watchTimelineUsers(int id) { + Stream> watchTimelineUsers(String id) { return db.users .filter() .inTimelineEqualTo(true) .or() - .isarIdEqualTo(id) - .isarIdProperty() + .idEqualTo(id) + .idProperty() .watch(); } @override - Stream watchArchiveTimeline(int userId) { + Stream watchArchiveTimeline(String userId) { final query = db.assets .where() - .ownerIdEqualToAnyChecksum(userId) + .ownerIdEqualToAnyChecksum(fastHash(userId)) .filter() .isArchivedEqualTo(true) .isTrashedEqualTo(false) @@ -52,10 +53,10 @@ class TimelineRepository extends DatabaseRepository } @override - Stream watchFavoriteTimeline(int userId) { + Stream watchFavoriteTimeline(String userId) { final query = db.assets .where() - .ownerIdEqualToAnyChecksum(userId) + .ownerIdEqualToAnyChecksum(fastHash(userId)) .filter() .isFavoriteEqualTo(true) .isTrashedEqualTo(false) @@ -79,10 +80,10 @@ class TimelineRepository extends DatabaseRepository } @override - Stream watchTrashTimeline(int userId) { + Stream watchTrashTimeline(String userId) { final query = db.assets .filter() - .ownerIdEqualTo(userId) + .ownerIdEqualTo(fastHash(userId)) .isTrashedEqualTo(true) .sortByFileCreatedAtDesc(); @@ -103,12 +104,12 @@ class TimelineRepository extends DatabaseRepository @override Stream watchHomeTimeline( - int userId, + String userId, GroupAssetsBy groupAssetByOption, ) { final query = db.assets .where() - .ownerIdEqualToAnyChecksum(userId) + .ownerIdEqualToAnyChecksum(fastHash(userId)) .filter() .isArchivedEqualTo(false) .isTrashedEqualTo(false) @@ -120,12 +121,13 @@ class TimelineRepository extends DatabaseRepository @override Stream watchMultiUsersTimeline( - List userIds, + List userIds, GroupAssetsBy groupAssetByOption, ) { + final isarUserIds = userIds.map(fastHash).toList(); final query = db.assets .where() - .anyOf(userIds, (qb, userId) => qb.ownerIdEqualToAnyChecksum(userId)) + .anyOf(isarUserIds, (qb, id) => qb.ownerIdEqualToAnyChecksum(id)) .filter() .isArchivedEqualTo(false) .isTrashedEqualTo(false) @@ -143,12 +145,12 @@ class TimelineRepository extends DatabaseRepository } @override - Stream watchAssetSelectionTimeline(int userId) { + Stream watchAssetSelectionTimeline(String userId) { final query = db.assets .where() .remoteIdIsNotNull() .filter() - .ownerIdEqualTo(userId) + .ownerIdEqualTo(fastHash(userId)) .isTrashedEqualTo(false) .stackPrimaryAssetIdIsNull() .sortByFileCreatedAtDesc(); diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index 1251ef51fe..0922f506d5 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -28,6 +28,7 @@ import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/services/entity.service.dart'; import 'package:immich_mobile/services/sync.service.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:logging/logging.dart'; final albumServiceProvider = Provider( @@ -208,7 +209,7 @@ class AlbumService { final Album album = await _albumApiRepository.create( albumName, assetIds: assets.map((asset) => asset.remoteId!), - sharedUserIds: sharedUsers.map((user) => user.uid), + sharedUserIds: sharedUsers.map((user) => user.id), ); await _entityService.fillAlbumWithDatabaseEntities(album); return _albumRepository.create(album); @@ -296,7 +297,7 @@ class AlbumService { Future deleteAlbum(Album album) async { try { final userId = _userService.getMyUser().id; - if (album.owner.value?.isarId == userId) { + if (album.owner.value?.isarId == fastHash(userId)) { await _albumApiRepository.delete(album.remoteId!); } if (album.shared) { @@ -362,7 +363,7 @@ class AlbumService { try { await _albumApiRepository.removeUser( album.remoteId!, - userId: user.uid, + userId: user.id, ); album.sharedUsers.remove(entity.User.fromDto(user)); diff --git a/mobile/lib/services/asset.service.dart b/mobile/lib/services/asset.service.dart index 6eff80ae02..2894f5a7de 100644 --- a/mobile/lib/services/asset.service.dart +++ b/mobile/lib/services/asset.service.dart @@ -101,7 +101,7 @@ class AssetService { _getRemoteAssetChanges(List users, DateTime since) async { final dto = AssetDeltaSyncDto( updatedAfter: since, - userIds: users.map((e) => e.uid).toList(), + userIds: users.map((e) => e.id).toList(), ); final changes = await _apiService.syncApi.getDeltaSync(dto); return changes == null || changes.needsFullSync @@ -142,7 +142,7 @@ class AssetService { limit: chunkSize, updatedUntil: until, lastId: lastId, - userId: user.uid, + userId: user.id, ); log.fine("Requesting $chunkSize assets from $lastId"); final List? assets = diff --git a/mobile/lib/services/partner.service.dart b/mobile/lib/services/partner.service.dart index cc3631a0b1..78d4d71e99 100644 --- a/mobile/lib/services/partner.service.dart +++ b/mobile/lib/services/partner.service.dart @@ -46,10 +46,10 @@ class PartnerService { Future removePartner(UserDto partner) async { try { - await _partnerApiRepository.delete(partner.uid); + await _partnerApiRepository.delete(partner.id); await _userRepository.update(partner.copyWith(isPartnerSharedBy: false)); } catch (e) { - _log.warning("Failed to remove partner ${partner.uid}", e); + _log.warning("Failed to remove partner ${partner.id}", e); return false; } return true; @@ -57,11 +57,11 @@ class PartnerService { Future addPartner(UserDto partner) async { try { - await _partnerApiRepository.create(partner.uid); + await _partnerApiRepository.create(partner.id); await _userRepository.update(partner.copyWith(isPartnerSharedBy: true)); return true; } catch (e) { - _log.warning("Failed to add partner ${partner.uid}", e); + _log.warning("Failed to add partner ${partner.id}", e); } return false; } @@ -72,14 +72,14 @@ class PartnerService { }) async { try { final dto = await _partnerApiRepository.update( - partner.uid, + partner.id, inTimeline: inTimeline, ); await _userRepository .update(partner.copyWith(inTimeline: dto.inTimeline)); return true; } catch (e) { - _log.warning("Failed to update partner ${partner.uid}", e); + _log.warning("Failed to update partner ${partner.id}", e); } return false; } diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart index 24f2fd00d6..1386eebec7 100644 --- a/mobile/lib/services/sync.service.dart +++ b/mobile/lib/services/sync.service.dart @@ -32,6 +32,7 @@ import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/datetime_comparison.dart'; import 'package:immich_mobile/utils/diff.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:logging/logging.dart'; final syncServiceProvider = Provider( @@ -152,14 +153,14 @@ class SyncService { /// Syncs users from the server to the local database /// Returns `true`if there were any changes Future _syncUsersFromServer(List users) async { - users.sortBy((u) => u.uid); + users.sortBy((u) => u.id); final dbUsers = await _userRepository.getAll(sortBy: SortUserBy.id); - final List toDelete = []; + final List toDelete = []; final List toUpsert = []; final changes = diffSortedListsSync( users, dbUsers, - compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid), + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), both: (UserDto a, UserDto b) { if (!a.updatedAt.isAtSameMomentAs(b.updatedAt) || a.isPartnerSharedBy != b.isPartnerSharedBy || @@ -319,12 +320,12 @@ class SyncService { } Future _updateUserAssetsETag(List users, DateTime time) { - final etags = users.map((u) => ETag(id: u.uid, time: time)).toList(); + final etags = users.map((u) => ETag(id: u.id, time: time)).toList(); return _eTagRepository.upsertAll(etags); } Future _clearUserAssetsETag(List users) { - final ids = users.map((u) => u.uid).toList(); + final ids = users.map((u) => u.id).toList(); return _eTagRepository.deleteByIds(ids); } @@ -408,7 +409,7 @@ class SyncService { sharedUsers, compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), both: (a, b) => false, - onlyFirst: (UserDto a) => userIdsToAdd.add(a.uid), + onlyFirst: (UserDto a) => userIdsToAdd.add(a.id), onlySecond: (UserDto a) => usersToUnlink.add(a), ); @@ -459,7 +460,8 @@ class SyncService { existing.addAll(foreign); // delete assets in DB unless they belong to this user or part of some other shared album - deleteCandidates.addAll(toUnlink.where((a) => a.ownerId != userId)); + final isarUserId = fastHash(userId); + deleteCandidates.addAll(toUnlink.where((a) => a.ownerId != isarUserId)); } return true; @@ -878,16 +880,16 @@ class SyncService { return null; } - users.sortBy((u) => u.uid); - sharedBy.sortBy((u) => u.uid); - sharedWith.sortBy((u) => u.uid); + users.sortBy((u) => u.id); + sharedBy.sortBy((u) => u.id); + sharedWith.sortBy((u) => u.id); final updatedSharedBy = []; diffSortedListsSync( users, sharedBy, - compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid), + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), both: (UserDto a, UserDto b) { updatedSharedBy.add(a.copyWith(isPartnerSharedBy: true)); return true; @@ -901,7 +903,7 @@ class SyncService { diffSortedListsSync( updatedSharedBy, sharedWith, - compare: (UserDto a, UserDto b) => a.uid.compareTo(b.uid), + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), both: (UserDto a, UserDto b) { updatedSharedWith.add( a.copyWith(inTimeline: b.inTimeline, isPartnerSharedWith: true), diff --git a/mobile/lib/services/timeline.service.dart b/mobile/lib/services/timeline.service.dart index dd2252602d..4e91d27a7c 100644 --- a/mobile/lib/services/timeline.service.dart +++ b/mobile/lib/services/timeline.service.dart @@ -28,21 +28,21 @@ class TimelineService { this._userService, ); - Future> getTimelineUserIds() async { + Future> getTimelineUserIds() async { final me = _userService.getMyUser(); return _timelineRepository.getTimelineUserIds(me.id); } - Stream> watchTimelineUserIds() async* { + Stream> watchTimelineUserIds() async* { final me = _userService.getMyUser(); yield* _timelineRepository.watchTimelineUsers(me.id); } - Stream watchHomeTimeline(int userId) { + Stream watchHomeTimeline(String userId) { return _timelineRepository.watchHomeTimeline(userId, _getGroupByOption()); } - Stream watchMultiUsersTimeline(List userIds) { + Stream watchMultiUsersTimeline(List userIds) { return _timelineRepository.watchMultiUsersTimeline( userIds, _getGroupByOption(), @@ -83,10 +83,10 @@ class TimelineService { GroupAssetsBy? groupBy, ) { GroupAssetsBy groupOption = GroupAssetsBy.none; - if (groupBy != null) { - groupOption = groupBy; - } else { + if (groupBy == null) { groupOption = _getGroupByOption(); + } else { + groupOption = groupBy; } return _timelineRepository.getTimelineFromAssets( diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index d2f0a2ac9d..3e73ab445b 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -6,13 +6,33 @@ import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:isar/isar.dart'; -const int targetVersion = 8; +const int targetVersion = 9; Future migrateDatabaseIfNeeded(Isar db) async { final int version = Store.get(StoreKey.version, 1); + + if (version < 9) { + await Store.put(StoreKey.version, version); + final value = await db.storeValues.get(StoreKey.currentUser.id); + if (value != null) { + final id = value.intValue; + if (id == null) { + return; + } + await db.writeTxn(() async { + final user = await db.users.get(id); + await db.storeValues + .put(StoreValue(StoreKey.currentUser.id, strValue: user?.id)); + }); + } + // Do not clear other entities + return; + } + if (version < targetVersion) { _migrateTo(db, targetVersion); } diff --git a/mobile/lib/widgets/album/album_thumbnail_card.dart b/mobile/lib/widgets/album/album_thumbnail_card.dart index 089544a9f1..30e2fc58b9 100644 --- a/mobile/lib/widgets/album/album_thumbnail_card.dart +++ b/mobile/lib/widgets/album/album_thumbnail_card.dart @@ -58,7 +58,7 @@ class AlbumThumbnailCard extends ConsumerWidget { // Add the owner name to the subtitle String? owner; if (showOwner) { - if (album.ownerId == ref.read(currentUserProvider)?.uid) { + if (album.ownerId == ref.read(currentUserProvider)?.id) { owner = 'album_thumbnail_owned'.tr(); } else if (album.ownerName != null) { owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]); diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart index 1b225f106f..ea54c69e53 100644 --- a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/stack.service.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart'; @@ -49,7 +50,8 @@ class BottomGalleryBar extends ConsumerWidget { if (asset == null) { return const SizedBox(); } - final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.id; + final isOwner = + asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? ''); final showControls = ref.watch(showControlsProvider); final stackId = asset.stackId; diff --git a/mobile/lib/widgets/asset_viewer/description_input.dart b/mobile/lib/widgets/asset_viewer/description_input.dart index 844e4744b3..778212eabe 100644 --- a/mobile/lib/widgets/asset_viewer/description_input.dart +++ b/mobile/lib/widgets/asset_viewer/description_input.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:logging/logging.dart'; @@ -81,7 +82,7 @@ class DescriptionInput extends HookConsumerWidget { } return TextField( - enabled: owner?.id == asset.ownerId, + enabled: fastHash(owner?.id ?? '') == asset.ownerId, focusNode: focusNode, onTap: () => isFocus.value = true, onChanged: (value) { diff --git a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart index dcef6c79d4..4354dceb4f 100644 --- a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart +++ b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart @@ -16,6 +16,7 @@ import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/trash.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart'; import 'package:immich_mobile/widgets/asset_viewer/top_control_app_bar.dart'; @@ -33,12 +34,13 @@ class GalleryAppBar extends ConsumerWidget { return const SizedBox(); } final album = ref.watch(currentAlbumProvider); - final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.id; + final isOwner = + asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? ''); final showControls = ref.watch(showControlsProvider); final isPartner = ref .watch(partnerSharedWithProvider) - .map((e) => e.id) + .map((e) => fastHash(e.id)) .contains(asset.ownerId); toggleFavorite(Asset asset) => diff --git a/mobile/lib/widgets/common/user_avatar.dart b/mobile/lib/widgets/common/user_avatar.dart index 753a1f5d37..a5a6fa2bdd 100644 --- a/mobile/lib/widgets/common/user_avatar.dart +++ b/mobile/lib/widgets/common/user_avatar.dart @@ -8,7 +8,7 @@ import 'package:immich_mobile/services/api.service.dart'; Widget userAvatar(BuildContext context, UserDto u, {double? radius}) { final url = - "${Store.get(StoreKey.serverEndpoint)}/users/${u.uid}/profile-image"; + "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image"; final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : ""; return CircleAvatar( radius: radius, diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart index 4bc7bfa0f4..b3727e8323 100644 --- a/mobile/lib/widgets/common/user_circle_avatar.dart +++ b/mobile/lib/widgets/common/user_circle_avatar.dart @@ -27,7 +27,7 @@ class UserCircleAvatar extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { bool isDarkTheme = context.themeData.brightness == Brightness.dark; final profileImageUrl = - '${Store.get(StoreKey.serverEndpoint)}/users/${user.uid}/profile-image?d=${Random().nextInt(1024)}'; + '${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${Random().nextInt(1024)}'; final textIcon = DefaultTextStyle( style: TextStyle( diff --git a/mobile/test/fixtures/user.stub.dart b/mobile/test/fixtures/user.stub.dart index 88b14dc02e..1dfec9b4b1 100644 --- a/mobile/test/fixtures/user.stub.dart +++ b/mobile/test/fixtures/user.stub.dart @@ -4,7 +4,7 @@ abstract final class UserStub { const UserStub._(); static final admin = UserDto( - uid: "admin", + id: "admin", email: "admin@test.com", name: "admin", isAdmin: true, @@ -14,7 +14,7 @@ abstract final class UserStub { ); static final user1 = UserDto( - uid: "user1", + id: "user1", email: "user1@test.com", name: "user1", isAdmin: false, @@ -24,7 +24,7 @@ abstract final class UserStub { ); static final user2 = UserDto( - uid: "user2", + id: "user2", email: "user2@test.com", name: "user2", isAdmin: false, diff --git a/mobile/test/modules/shared/sync_service_test.dart b/mobile/test/modules/shared/sync_service_test.dart index 7594c35fff..0197008dd1 100644 --- a/mobile/test/modules/shared/sync_service_test.dart +++ b/mobile/test/modules/shared/sync_service_test.dart @@ -66,7 +66,7 @@ void main() { final MockUserService userService = MockUserService(); final owner = UserDto( - uid: "1", + id: "1", updatedAt: DateTime.now(), email: "a@b.c", name: "first last", @@ -110,7 +110,7 @@ void main() { ); when(() => userService.getMyUser()).thenReturn(owner); when(() => eTagRepository.get(owner.id)) - .thenAnswer((_) async => ETag(id: owner.uid, time: DateTime.now())); + .thenAnswer((_) async => ETag(id: owner.id, time: DateTime.now())); when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {}); when(() => eTagRepository.upsertAll(any())).thenAnswer((_) async {}); when(() => partnerRepository.getSharedWith()).thenAnswer((_) async => []); diff --git a/mobile/test/services/album.service_test.dart b/mobile/test/services/album.service_test.dart index 0efd695560..443e37e75d 100644 --- a/mobile/test/services/album.service_test.dart +++ b/mobile/test/services/album.service_test.dart @@ -149,7 +149,7 @@ void main() { () => albumApiRepository.create( "name", assetIds: [AssetStub.image1.remoteId!], - sharedUserIds: [UserStub.user1.uid], + sharedUserIds: [UserStub.user1.id], ), ).called(1); verify( @@ -217,7 +217,7 @@ void main() { final result = await sut.addUsers( AlbumStub.emptyAlbum, - [UserStub.user2.uid], + [UserStub.user2.id], ); expect(result, true); diff --git a/mobile/test/services/entity.service_test.dart b/mobile/test/services/entity.service_test.dart index fd99a4faee..190b2bc2b5 100644 --- a/mobile/test/services/entity.service_test.dart +++ b/mobile/test/services/entity.service_test.dart @@ -41,7 +41,7 @@ void main() { [User.fromDto(UserStub.admin), User.fromDto(UserStub.admin)], ); - when(() => userRepository.get(any())) + when(() => userRepository.getByUserId(any())) .thenAnswer((_) async => UserStub.admin); when(() => userRepository.getByUserId(any())) .thenAnswer((_) async => UserStub.admin); From b609f3584150aba3f20ae8149edf4676f3b5e609 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 11:07:11 -0500 Subject: [PATCH 16/18] chore(deps): update base-image to v20250318 (major) (#16950) * chore(deps): update base-image to v20250318 * chore --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Alex --- mobile/lib/widgets/common/immich_app_bar.dart | 13 ------------- server/Dockerfile | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart index c776f7aa53..51b4faa014 100644 --- a/mobile/lib/widgets/common/immich_app_bar.dart +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -7,7 +7,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; -import 'package:immich_mobile/providers/immich_logo_provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -28,7 +27,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; final ServerInfo serverInfoState = ref.watch(serverInfoProvider); - final immichLogo = ref.watch(immichLogoProvider); final user = ref.watch(currentUserProvider); final isDarkTheme = context.isDarkTheme; const widgetSize = 30.0; @@ -157,17 +155,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { children: [ Builder( builder: (context) { - final today = DateTime.now(); - if (today.month == 4 && today.day == 1) { - if (immichLogo.value == null) { - return const SizedBox.shrink(); - } - return Image.memory( - immichLogo.value!, - fit: BoxFit.cover, - height: 80, - ); - } return Padding( padding: const EdgeInsets.only(top: 3.0), child: SvgPicture.asset( diff --git a/server/Dockerfile b/server/Dockerfile index 83e750a3f9..f4f58f217d 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20250311@sha256:dd270c9fb535ea4d216241d6984e20ef4b4bc3514658a045e5a1c3821aa44507 AS dev +FROM ghcr.io/immich-app/base-server-dev:20250318@sha256:b320205cbe1851565620f2bf28fa7bc47147d8d4fdd852116c4426fa69dc555f AS dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -42,7 +42,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20250311@sha256:8c96b3df2161806c9dd6032356ca04c28d374057462b3964c8e8d3cc35c035ee +FROM ghcr.io/immich-app/base-server-prod:20250318@sha256:919fa00d2d6aacb4b030ea8d2ce34a6e0d302923c02f5a6f0cdc835ecfdd4d78 WORKDIR /usr/src/app ENV NODE_ENV=production \ From fe19f9ba84c68e50d46da61fb5279507b6ebb898 Mon Sep 17 00:00:00 2001 From: Andreas Date: Wed, 19 Mar 2025 03:34:09 +1100 Subject: [PATCH 17/18] fix(web): asset selection on memories page is broken (#16759) * 16712: Proper intialisation of the memory store to avoid loading up duplicate object refs of the same asset. * 16712: Add auth to memory mapping so isFavorite is actually return correctly from the server. * 16712: Move logic that belongs in the store into the store. * 16712: Cleanup. * 16712: Fix init behaviour. * 16712: Add comment. * 16712: Make method private. * 16712: Fix import. * 16712: Fix format. * 16712: Cleaner if/else and fix typo. * fix: icon size mismatch * 16712: Fixed up state machine managing memory playback: * Updated to `Tween` (`tweened` was deprecated) * Removed `resetPromise`. Setting progressController to 0 had the same effect, so not really sure why it was there? * Removed the many duplicate places the `handleAction` method was called. Now we just called it on `afterNavigate` as well as when `galleryInView` or `$isViewing` state changes. * 16712: Add aria tag. * 16712: Fix memory player duplicate invocation bugs. Now we should only call 'reset' and 'play' once, after navigate/page load. This should hopefully fix all the various bugs around playback. * 16712: Cleanup * 16712: Cleanup * 16712: Cleanup * 16712: Cleanup --------- Co-authored-by: Alex Tran --- i18n/en.json | 2 + server/src/dtos/memory.dto.ts | 5 +- server/src/services/memory.service.ts | 8 +- .../memory-page/memory-viewer.svelte | 355 ++++++++---------- .../components/photos-page/memory-lane.svelte | 44 ++- web/src/lib/stores/memory.store.svelte.ts | 119 ++++++ web/src/lib/stores/memory.store.ts | 11 - 7 files changed, 305 insertions(+), 239 deletions(-) create mode 100644 web/src/lib/stores/memory.store.svelte.ts delete mode 100644 web/src/lib/stores/memory.store.ts diff --git a/i18n/en.json b/i18n/en.json index e8726b3d20..e08c8ec545 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1082,7 +1082,9 @@ "remove_url": "Remove URL", "remove_user": "Remove user", "removed_api_key": "Removed API Key: {name}", + "remove_memory": "Remove memory", "removed_memory": "Removed memory", + "remove_photo_from_memory": "Remove photo from this memory", "removed_photo_from_memory": "Removed photo from memory", "removed_from_archive": "Removed from archive", "removed_from_favorites": "Removed from favorites", diff --git a/server/src/dtos/memory.dto.ts b/server/src/dtos/memory.dto.ts index 9eef78d4d0..36f4631ef5 100644 --- a/server/src/dtos/memory.dto.ts +++ b/server/src/dtos/memory.dto.ts @@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; import { AssetEntity } from 'src/entities/asset.entity'; import { MemoryType } from 'src/enum'; import { MemoryItem } from 'src/types'; @@ -88,7 +89,7 @@ export class MemoryResponseDto { assets!: AssetResponseDto[]; } -export const mapMemory = (entity: MemoryItem): MemoryResponseDto => { +export const mapMemory = (entity: MemoryItem, auth: AuthDto): MemoryResponseDto => { return { id: entity.id, createdAt: entity.createdAt, @@ -102,6 +103,6 @@ export const mapMemory = (entity: MemoryItem): MemoryResponseDto => { type: entity.type as MemoryType, data: entity.data as unknown as MemoryData, isSaved: entity.isSaved, - assets: ('assets' in entity ? entity.assets : []).map((asset) => mapAsset(asset as AssetEntity)), + assets: ('assets' in entity ? entity.assets : []).map((asset) => mapAsset(asset as AssetEntity, { auth })), }; }; diff --git a/server/src/services/memory.service.ts b/server/src/services/memory.service.ts index 28c90f6576..8ad3c27b4d 100644 --- a/server/src/services/memory.service.ts +++ b/server/src/services/memory.service.ts @@ -74,13 +74,13 @@ export class MemoryService extends BaseService { async search(auth: AuthDto, dto: MemorySearchDto) { const memories = await this.memoryRepository.search(auth.user.id, dto); - return memories.map((memory) => mapMemory(memory)); + return memories.map((memory) => mapMemory(memory, auth)); } async get(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.MEMORY_READ, ids: [id] }); const memory = await this.findOrFail(id); - return mapMemory(memory); + return mapMemory(memory, auth); } async create(auth: AuthDto, dto: MemoryCreateDto) { @@ -104,7 +104,7 @@ export class MemoryService extends BaseService { allowedAssetIds, ); - return mapMemory(memory); + return mapMemory(memory, auth); } async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise { @@ -116,7 +116,7 @@ export class MemoryService extends BaseService { seenAt: dto.seenAt, }); - return mapMemory(memory); + return mapMemory(memory, auth); } async remove(auth: AuthDto, id: string): Promise { diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 9a6ad628a3..ceaabd8387 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -1,6 +1,6 @@ @@ -350,24 +315,24 @@ - + - + {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} {/if} - +
{/if}
- {#if current && current.memory.assets.length > 0} + {#if current} goto(AppRoute.PHOTOS)} forceDark multiRow> {#snippet leading()} {#if current} @@ -381,22 +346,14 @@ handleAction(paused ? 'play' : 'pause')} + onclick={() => handlePromiseError(handleAction('PlayPauseButtonClick', paused ? 'play' : 'pause'))} class="hover:text-black" /> {#each current.memory.assets as asset, index (asset.id)} - + - {#await resetPromise} - - {:then} - current.assetIndex ? 0 : $progressBarController * 100}%`} - > - {/await} + {/each} @@ -474,7 +431,7 @@
{#key current.asset.id}
- {#if current.asset.type == AssetTypeEnum.Video} + {#if current.asset.type === AssetTypeEnum.Video}
diff --git a/web/src/lib/stores/memory.store.svelte.ts b/web/src/lib/stores/memory.store.svelte.ts new file mode 100644 index 0000000000..76e406682d --- /dev/null +++ b/web/src/lib/stores/memory.store.svelte.ts @@ -0,0 +1,119 @@ +import { asLocalTimeISO } from '$lib/utils/date-time'; +import { + type AssetResponseDto, + deleteMemory, + type MemoryResponseDto, + removeMemoryAssets, + searchMemories, + updateMemory, +} from '@immich/sdk'; +import { DateTime } from 'luxon'; + +type MemoryIndex = { + memoryIndex: number; + assetIndex: number; +}; + +export type MemoryAsset = MemoryIndex & { + memory: MemoryResponseDto; + asset: AssetResponseDto; + previousMemory?: MemoryResponseDto; + previous?: MemoryAsset; + next?: MemoryAsset; + nextMemory?: MemoryResponseDto; +}; + +class MemoryStoreSvelte { + memories = $state([]); + private initialized = false; + private memoryAssets = $derived.by(() => { + const memoryAssets: MemoryAsset[] = []; + let previous: MemoryAsset | undefined; + for (const [memoryIndex, memory] of this.memories.entries()) { + for (const [assetIndex, asset] of memory.assets.entries()) { + const current = { + memory, + memoryIndex, + previousMemory: this.memories[memoryIndex - 1], + nextMemory: this.memories[memoryIndex + 1], + asset, + assetIndex, + previous, + }; + + memoryAssets.push(current); + + if (previous) { + previous.next = current; + } + + previous = current; + } + } + + return memoryAssets; + }); + + getMemoryAsset(assetId: string | undefined) { + return this.memoryAssets.find((memoryAsset) => memoryAsset.asset.id === assetId) ?? this.memoryAssets[0]; + } + + hideAssetsFromMemory(ids: string[]) { + const idSet = new Set(ids); + for (const memory of this.memories) { + memory.assets = memory.assets.filter((asset) => !idSet.has(asset.id)); + } + // if we removed all assets from a memory, then lets remove those memories (we don't show memories with 0 assets) + this.memories = this.memories.filter((memory) => memory.assets.length > 0); + } + + async deleteMemory(id: string) { + const memory = this.memories.find((memory) => memory.id === id); + if (memory) { + await deleteMemory({ id: memory.id }); + this.memories = this.memories.filter((memory) => memory.id !== id); + } + } + + async deleteAssetFromMemory(assetId: string) { + const memoryWithAsset = this.memories.find((memory) => memory.assets.some((asset) => asset.id === assetId)); + + if (memoryWithAsset) { + if (memoryWithAsset.assets.length === 1) { + await this.deleteMemory(memoryWithAsset.id); + } else { + await removeMemoryAssets({ id: memoryWithAsset.id, bulkIdsDto: { ids: [assetId] } }); + memoryWithAsset.assets = memoryWithAsset.assets.filter((asset) => asset.id !== assetId); + } + } + } + + async updateMemorySaved(id: string, isSaved: boolean) { + const memory = this.memories.find((memory) => memory.id === id); + if (memory) { + await updateMemory({ + id, + memoryUpdateDto: { + isSaved, + }, + }); + memory.isSaved = isSaved; + } + } + + async initialize() { + if (this.initialized) { + return; + } + this.initialized = true; + + await this.loadAllMemories(); + } + + private async loadAllMemories() { + const memories = await searchMemories({ $for: asLocalTimeISO(DateTime.now()) }); + this.memories = memories.filter((memory) => memory.assets.length > 0); + } +} + +export const memoryStore = new MemoryStoreSvelte(); diff --git a/web/src/lib/stores/memory.store.ts b/web/src/lib/stores/memory.store.ts deleted file mode 100644 index a927ab648a..0000000000 --- a/web/src/lib/stores/memory.store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { asLocalTimeISO } from '$lib/utils/date-time'; -import { searchMemories, type MemoryResponseDto } from '@immich/sdk'; -import { DateTime } from 'luxon'; -import { writable } from 'svelte/store'; - -export const memoryStore = writable(); - -export const loadMemories = async () => { - const memories = await searchMemories({ $for: asLocalTimeISO(DateTime.now()) }); - memoryStore.set(memories); -}; From 9f46ba8eb49d57b52ea940de6d9254bc2d8dea1b Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Tue, 18 Mar 2025 12:42:09 -0400 Subject: [PATCH 18/18] fix(server): set pixel format when scaling and not tonemapping (#16932) set pixel format when scaling and not tonemapping --- server/src/services/media.service.spec.ts | 26 ++++++++++---- server/src/utils/media.ts | 41 +++++++++-------------- server/test/fixtures/media.stub.ts | 16 +++++++++ 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index d98cff866f..9b8cc820cc 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -1608,7 +1608,7 @@ describe(MediaService.name, () => { 'upload/encoded-video/user-id/as/se/asset-id.mp4', expect.objectContaining({ inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), - outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), + outputOptions: expect.arrayContaining([expect.stringContaining('scale_cuda=-2:720:format=nv12')]), twoPass: false, }), ); @@ -1766,9 +1766,7 @@ describe(MediaService.name, () => { '-threads 1', '-qsv_device /dev/dri/renderD128', ]), - outputOptions: expect.arrayContaining([ - expect.stringContaining('scale_qsv=-1:720:async_depth=4:mode=hq:format=nv12'), - ]), + outputOptions: expect.arrayContaining([expect.stringContaining('scale_qsv=-1:720:async_depth=4:mode=hq')]), twoPass: false, }), ); @@ -2010,9 +2008,7 @@ describe(MediaService.name, () => { '-threads 1', '-hwaccel_device /dev/dri/renderD128', ]), - outputOptions: expect.arrayContaining([ - expect.stringContaining('scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12'), - ]), + outputOptions: expect.arrayContaining([expect.stringContaining('scale_vaapi=-2:720:mode=hq:out_range=pc')]), twoPass: false, }), ); @@ -2368,6 +2364,22 @@ describe(MediaService.name, () => { ); }); + it('should convert to yuv420p when scaling without tone-mapping', async () => { + mocks.media.probe.mockResolvedValue(probeStub.videoStream4K10Bit); + mocks.systemMetadata.get.mockResolvedValue({ ffmpeg: { transcode: TranscodePolicy.REQUIRED } }); + mocks.asset.getByIds.mockResolvedValue([assetStub.video]); + await sut.handleVideoConversion({ id: assetStub.video.id }); + expect(mocks.media.transcode).toHaveBeenCalledWith( + '/original/path.ext', + 'upload/encoded-video/user-id/as/se/asset-id.mp4', + expect.objectContaining({ + inputOptions: expect.any(Array), + outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy', '-vf scale=-2:720,format=yuv420p']), + twoPass: false, + }), + ); + }); + it('should count frames for progress when log level is debug', async () => { mocks.media.probe.mockResolvedValue(probeStub.matroskaContainer); mocks.logger.isLevelEnabled.mockReturnValue(true); diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index 62cf6500fb..d26f8d0fb6 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -159,9 +159,11 @@ export class BaseConfig implements VideoCodecSWConfig { options.push(`scale=${this.getScaling(videoStream)}`); } - options.push(...this.getToneMapping(videoStream)); - if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { - options.push(`format=yuv420p`); + const tonemapOptions = this.getToneMapping(videoStream); + if (tonemapOptions.length > 0) { + options.push(...tonemapOptions); + } else if (!videoStream.pixelFormat.endsWith('420p')) { + options.push('format=yuv420p'); } return options; @@ -606,14 +608,13 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = []; - if (this.shouldScale(videoStream)) { + const tonemapOptions = this.getToneMapping(videoStream); + if (this.shouldScale(videoStream) || (tonemapOptions.length === 0 && !videoStream.pixelFormat.endsWith('420p'))) { options.push(`scale_cuda=${this.getScaling(videoStream)}`); } - options.push(...this.getToneMapping(videoStream)); + options.push(...tonemapOptions); if (options.length > 0) { options[options.length - 1] += ':format=nv12'; - } else if (!videoStream.pixelFormat.endsWith('420p')) { - options.push('format=nv12'); } return options; } @@ -732,17 +733,12 @@ export class QsvHwDecodeConfig extends QsvSwDecodeConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = []; const tonemapOptions = this.getToneMapping(videoStream); - if (this.shouldScale(videoStream) || tonemapOptions.length === 0) { - let scaling = `scale_qsv=${this.getScaling(videoStream)}:async_depth=4:mode=hq`; - if (tonemapOptions.length === 0) { - scaling += ':format=nv12'; - } - options.push(scaling); + if (tonemapOptions.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { + options.push(`scale_qsv=${this.getScaling(videoStream)}:async_depth=4:mode=hq:format=nv12`); + } else if (this.shouldScale(videoStream)) { + options.push(`scale_qsv=${this.getScaling(videoStream)}:async_depth=4:mode=hq`); } options.push(...tonemapOptions); - if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { - options.push('format=nv12'); - } return options; } @@ -848,17 +844,12 @@ export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { getFilterOptions(videoStream: VideoStreamInfo) { const options = []; const tonemapOptions = this.getToneMapping(videoStream); - if (this.shouldScale(videoStream) || tonemapOptions.length === 0) { - let scaling = `scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc`; - if (tonemapOptions.length === 0) { - scaling += ':format=nv12'; - } - options.push(scaling); + if (tonemapOptions.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { + options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc:format=nv12`); + } else if (this.shouldScale(videoStream)) { + options.push(`scale_vaapi=${this.getScaling(videoStream)}:mode=hq:out_range=pc`); } options.push(...tonemapOptions); - if (options.length === 0 && !videoStream.pixelFormat.endsWith('420p')) { - options.push('format=nv12'); - } return options; } diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index 021f899ae5..e1579435f5 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -134,6 +134,22 @@ export const probeStub = { }, ], }), + videoStream4K10Bit: Object.freeze({ + ...probeStubDefault, + videoStreams: [ + { + index: 0, + height: 2160, + width: 3840, + codecName: 'h264', + frameCount: 100, + rotation: 0, + isHDR: false, + bitrate: 0, + pixelFormat: 'yuv420p10le', + }, + ], + }), videoStreamVertical2160p: Object.freeze({ ...probeStubDefault, videoStreams: [