immich-app / immich

High performance self-hosted photo and video management solution.
https://immich.app
GNU Affero General Public License v3.0
44.67k stars 2.17k forks source link

Failed to generate preview for Adobe ACR exported JXL picture. #11938

Closed nidbCN closed 6 days ago

nidbCN commented 3 weeks ago

The bug

The immich server can't process Adobe ACR generated JXL picture. Here are the arguments that I export it:

屏幕截图 2024-08-20 151514

The export arguments are very normal, I set a sRGB color range, 8bit deepth, high quelity output and keep another things default. I turn on verbose logs and put it in this issue. I also try a JXL picture with lossless output and the resule was same as the loss one. The picture that I uploaded can be normally display on my PC with ImageGlass and can be process in ffmpeg with libjxl. And the archive I attached to this issue pic.zip is the picture that failed to generate preview.

The OS that Immich Server is running on

Ubuntu 24.04 with docker compose

Version of Immich Server

v1.111.0

Version of Immich Mobile App

-

Platform with the issue

Your docker-compose.yml content

name: web-services

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    extends:
      file: hwaccel.transcoding.yml
      service: quicksync # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
    volumes:
      # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - 2283:3001
    depends_on:
      - redis
      - database
    restart: always
    networks:
      - immich_backend
      - http_services

  immich-machine-learning:
    container_name: immich_machine_learning
    # For hardware acceleration, add one of -[armnn, cuda, openvino] 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
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
    networks:
      - immich_backend

  redis:
    container_name: immich_redis
    image: docker.io/redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always
    networks:
      - immich_backend

  database:
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --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"]
    restart: always
    networks:
      - immich_backend

volumes:
  model-cache:

networks:
  http_services:
    name: http_services_network
    external: false
  immich_backend:
    external: false

Your .env content

# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# The location where your uploaded files are stored
UPLOAD_LOCATION=/data/immich/storages
# The location where your database files are stored
DB_DATA_LOCATION=/data/immich/postgres

# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
TZ=Asia/Shanghai
IMMICH_LOG_LEVEL=verbose

# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release

# Connection secret for postgres. You should change it to a random password
DB_PASSWORD=f172958eae13cf9c

# The values below this line do not need to be changed
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

Reproduction steps

1. Use Adobe ACR to open a picture
2. Export JXL format, with sRGB, 8bit color deepth and quelity 9
3. Upload JXL picture to immich

Relevant log output

immich_server  | [Nest] 19  - 08/20/2024, 4:40:41 PM   DEBUG [Api:LoggingInterceptor~o00i4rzf] POST /api/assets/bulk-upload-check 200 2.86ms 111.33.226.91
immich_server  | [Nest] 19  - 08/20/2024, 4:40:41 PM VERBOSE [Api:LoggingInterceptor~o00i4rzf] {"assets":[{"id":"_5D34943_loss.JXL","checksum":"f4e4a6daf2a4b41dafebea803f0a4d29572b67c1"}]}
immich_server  | [Nest] 19  - 08/20/2024, 4:40:51 PM   DEBUG [Api:LoggingInterceptor~jh58uxjw] POST /api/assets 201 156.75ms 111.33.226.91
immich_server  | [Nest] 19  - 08/20/2024, 4:40:51 PM VERBOSE [Api:LoggingInterceptor~jh58uxjw] {"deviceAssetId":"web-_5D34943_loss.JXL-1724138134800","deviceId":"WEB","fileCreatedAt":"2024-08-20T07:15:34.800Z","fileModifiedAt":"2024-08-20T07:15:34.800Z","isFavorite":"false","duration":"0:00:00.000000"}
immich_server  | [Nest] 7  - 08/20/2024, 4:40:52 PM VERBOSE [Microservices:MetadataService] Exif Tags
immich_server  | [Nest] 7  - 08/20/2024, 4:40:52 PM VERBOSE [Microservices:MetadataService] Object:
immich_server  | {
immich_server  |   "SourceFile": "/usr/src/app/upload/upload/a085767f-89b2-4f0a-94b0-72c819103657/0c/4b/0c4b6997-f49e-4f57-aaf7-46192c975fe5.JXL",
immich_server  |   "errors": [],
immich_server  |   "tz": "UTC+8",
immich_server  |   "tzSource": "OffsetTime",
immich_server  |   "FocalLength": 200,
immich_server  |   "ExifToolVersion": 12.91,
immich_server  |   "FileName": "0c4b6997-f49e-4f57-aaf7-46192c975fe5.JXL",
immich_server  |   "Directory": "/usr/src/app/upload/upload/a085767f-89b2-4f0a-94b0-72c819103657/0c/4b",
immich_server  |   "FileSize": "4.9 MB",
immich_server  |   "FileModifyDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 8,
immich_server  |     "day": 20,
immich_server  |     "hour": 7,
immich_server  |     "minute": 15,
immich_server  |     "second": 34,
immich_server  |     "tzoffsetMinutes": 0,
immich_server  |     "rawValue": "2024:08:20 07:15:34+00:00",
immich_server  |     "zoneName": "UTC",
immich_server  |     "inferredZone": false
immich_server  |   },
immich_server  |   "FileAccessDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 8,
immich_server  |     "day": 20,
immich_server  |     "hour": 8,
immich_server  |     "minute": 40,
immich_server  |     "second": 51,
immich_server  |     "tzoffsetMinutes": 0,
immich_server  |     "rawValue": "2024:08:20 08:40:51+00:00",
immich_server  |     "zoneName": "UTC",
immich_server  |     "inferredZone": false
immich_server  |   },
immich_server  |   "FileInodeChangeDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 8,
immich_server  |     "day": 20,
immich_server  |     "hour": 8,
immich_server  |     "minute": 40,
immich_server  |     "second": 51,
immich_server  |     "tzoffsetMinutes": 0,
immich_server  |     "rawValue": "2024:08:20 08:40:51+00:00",
immich_server  |     "zoneName": "UTC",
immich_server  |     "inferredZone": false
immich_server  |   },
immich_server  |   "FilePermissions": "-rw-r--r--",
immich_server  |   "FileType": "JXL",
immich_server  |   "FileTypeExtension": "jxl",
immich_server  |   "MIMEType": "image/jxl",
immich_server  |   "MajorBrand": "JPEG XL Image (.JXL)",
immich_server  |   "MinorVersion": "0.0.0",
immich_server  |   "CompatibleBrands": [
immich_server  |     "jxl "
immich_server  |   ],
immich_server  |   "ImageWidth": 5760,
immich_server  |   "ImageHeight": 3840,
immich_server  |   "ExifByteOrder": "Little-endian (Intel, II)",
immich_server  |   "Make": "Canon",
immich_server  |   "Model": "Canon EOS 5D Mark III",
immich_server  |   "Software": "Adobe Photoshop Camera Raw 16.3.1 (Windows)",
immich_server  |   "Artist": "Gaein nidb/gaein.cn",
immich_server  |   "ExposureTime": "1/100",
immich_server  |   "FNumber": 9,
immich_server  |   "ExposureProgram": "Aperture-priority AE",
immich_server  |   "ISO": 50,
immich_server  |   "SensitivityType": "Recommended Exposure Index",
immich_server  |   "RecommendedExposureIndex": 50,
immich_server  |   "ExifVersion": "0231",
immich_server  |   "OffsetTime": "+08:00",
immich_server  |   "ShutterSpeedValue": "1/100",
immich_server  |   "ApertureValue": 9,
immich_server  |   "ExposureCompensation": 0,
immich_server  |   "MaxApertureValue": 4,
immich_server  |   "MeteringMode": "Multi-segment",
immich_server  |   "Flash": "Off, Did not fire",
immich_server  |   "SubSecTimeOriginal": 20,
immich_server  |   "SubSecTimeDigitized": 20,
immich_server  |   "ColorSpace": "Uncalibrated",
immich_server  |   "FocalPlaneXResolution": 1600,
immich_server  |   "FocalPlaneYResolution": 1600,
immich_server  |   "FocalPlaneResolutionUnit": "cm",
immich_server  |   "CustomRendered": "Normal",
immich_server  |   "ExposureMode": "Auto",
immich_server  |   "WhiteBalance": "Auto",
immich_server  |   "SceneCaptureType": "Standard",
immich_server  |   "SerialNumber": 214020000681,
immich_server  |   "LensInfo": "70-200mm f/?",
immich_server  |   "LensModel": "EF70-200mm f/4L IS USM",
immich_server  |   "LensSerialNumber": "000082c081",
immich_server  |   "XMPToolkit": "Adobe XMP Core 7.0-c000 1.000000, 0000/00/00-00:00:00        ",
immich_server  |   "MetadataDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 8,
immich_server  |     "day": 20,
immich_server  |     "hour": 15,
immich_server  |     "minute": 15,
immich_server  |     "second": 27,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:08:20 15:15:27+08:00",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": false
immich_server  |   },
immich_server  |   "CreatorTool": "Adobe Photoshop Camera Raw 16.3.1 (Windows)",
immich_server  |   "Format": "image/jxl",
immich_server  |   "Lens": "EF70-200mm f/4L IS USM",
immich_server  |   "ImageNumber": 0,
immich_server  |   "ApproximateFocusDistance": "infinity",
immich_server  |   "FlashCompensation": 0,
immich_server  |   "Firmware": "1.2.3",
immich_server  |   "DistortionCorrectionAlreadyApplied": true,
immich_server  |   "LateralChromaticAberrationCorrectionAlreadyApplied": true,
immich_server  |   "DateCreated": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 5,
immich_server  |     "day": 31,
immich_server  |     "hour": 10,
immich_server  |     "minute": 9,
immich_server  |     "second": 29,
immich_server  |     "millisecond": 200,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:05:31 10:09:29.20",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": true
immich_server  |   },
immich_server  |   "DocumentID": "xmp.did:629f4687-fd5e-3148-a2a0-16a238f8ffdb",
immich_server  |   "OriginalDocumentID": "636A60734F7D67D33261FBA6084F3B59",
immich_server  |   "InstanceID": "xmp.iid:629f4687-fd5e-3148-a2a0-16a238f8ffdb",
immich_server  |   "RawFileName": "_5D34943.CR2",
immich_server  |   "Version": "16.3.1",
immich_server  |   "ProcessVersion": 15.4,
immich_server  |   "ColorTemperature": 4800,
immich_server  |   "Tint": "+7",
immich_server  |   "Exposure2012": "+0.03",
immich_server  |   "Contrast2012": "+7",
immich_server  |   "Highlights2012": -54,
immich_server  |   "Shadows2012": "+32",
immich_server  |   "Whites2012": "+27",
immich_server  |   "Blacks2012": -31,
immich_server  |   "Texture": 0,
immich_server  |   "Clarity2012": 0,
immich_server  |   "Dehaze": 0,
immich_server  |   "Vibrance": "+20",
immich_server  |   "Saturation": "+5",
immich_server  |   "ParametricShadows": 0,
immich_server  |   "ParametricDarks": 0,
immich_server  |   "ParametricLights": 0,
immich_server  |   "ParametricHighlights": 0,
immich_server  |   "ParametricShadowSplit": 25,
immich_server  |   "ParametricMidtoneSplit": 50,
immich_server  |   "ParametricHighlightSplit": 75,
immich_server  |   "Sharpness": 40,
immich_server  |   "SharpenRadius": "+1.0",
immich_server  |   "SharpenDetail": 25,
immich_server  |   "SharpenEdgeMasking": 0,
immich_server  |   "LuminanceSmoothing": 0,
immich_server  |   "ColorNoiseReduction": 25,
immich_server  |   "ColorNoiseReductionDetail": 50,
immich_server  |   "ColorNoiseReductionSmoothness": 50,
immich_server  |   "HueAdjustmentRed": 0,
immich_server  |   "HueAdjustmentOrange": 0,
immich_server  |   "HueAdjustmentYellow": 0,
immich_server  |   "HueAdjustmentGreen": 0,
immich_server  |   "HueAdjustmentAqua": 0,
immich_server  |   "HueAdjustmentBlue": 0,
immich_server  |   "HueAdjustmentPurple": 0,
immich_server  |   "HueAdjustmentMagenta": 0,
immich_server  |   "SaturationAdjustmentRed": 0,
immich_server  |   "SaturationAdjustmentOrange": 0,
immich_server  |   "SaturationAdjustmentYellow": 0,
immich_server  |   "SaturationAdjustmentGreen": 0,
immich_server  |   "SaturationAdjustmentAqua": 0,
immich_server  |   "SaturationAdjustmentBlue": 0,
immich_server  |   "SaturationAdjustmentPurple": 0,
immich_server  |   "SaturationAdjustmentMagenta": 0,
immich_server  |   "LuminanceAdjustmentRed": 0,
immich_server  |   "LuminanceAdjustmentOrange": 0,
immich_server  |   "LuminanceAdjustmentYellow": 0,
immich_server  |   "LuminanceAdjustmentGreen": 0,
immich_server  |   "LuminanceAdjustmentAqua": 0,
immich_server  |   "LuminanceAdjustmentBlue": 0,
immich_server  |   "LuminanceAdjustmentPurple": 0,
immich_server  |   "LuminanceAdjustmentMagenta": 0,
immich_server  |   "SplitToningShadowHue": 0,
immich_server  |   "SplitToningShadowSaturation": 0,
immich_server  |   "SplitToningHighlightHue": 0,
immich_server  |   "SplitToningHighlightSaturation": 0,
immich_server  |   "SplitToningBalance": 0,
immich_server  |   "ColorGradeMidtoneHue": 0,
immich_server  |   "ColorGradeMidtoneSat": 0,
immich_server  |   "ColorGradeShadowLum": 0,
immich_server  |   "ColorGradeMidtoneLum": 0,
immich_server  |   "ColorGradeHighlightLum": 0,
immich_server  |   "ColorGradeBlending": 50,
immich_server  |   "ColorGradeGlobalHue": 0,
immich_server  |   "ColorGradeGlobalSat": 0,
immich_server  |   "ColorGradeGlobalLum": 0,
immich_server  |   "AutoLateralCA": 1,
immich_server  |   "LensProfileEnable": 1,
immich_server  |   "LensManualDistortionAmount": 0,
immich_server  |   "VignetteAmount": 0,
immich_server  |   "DefringePurpleAmount": 0,
immich_server  |   "DefringePurpleHueLo": 30,
immich_server  |   "DefringePurpleHueHi": 70,
immich_server  |   "DefringeGreenAmount": 0,
immich_server  |   "DefringeGreenHueLo": 40,
immich_server  |   "DefringeGreenHueHi": 60,
immich_server  |   "PerspectiveUpright": "Auto",
immich_server  |   "PerspectiveVertical": 0,
immich_server  |   "PerspectiveHorizontal": 0,
immich_server  |   "PerspectiveRotate": 0,
immich_server  |   "PerspectiveAspect": 0,
immich_server  |   "PerspectiveScale": 100,
immich_server  |   "PerspectiveX": 0,
immich_server  |   "PerspectiveY": 0,
immich_server  |   "GrainAmount": 0,
immich_server  |   "PostCropVignetteAmount": 0,
immich_server  |   "ShadowTint": 0,
immich_server  |   "RedHue": 0,
immich_server  |   "RedSaturation": 0,
immich_server  |   "GreenHue": 0,
immich_server  |   "GreenSaturation": 0,
immich_server  |   "BlueHue": 0,
immich_server  |   "BlueSaturation": 0,
immich_server  |   "HDREditMode": 0,
immich_server  |   "ConvertToGrayscale": false,
immich_server  |   "OverrideLookVignette": false,
immich_server  |   "ToneCurveName2012": "Linear",
immich_server  |   "CameraProfile": "Camera Standard",
immich_server  |   "CameraProfileDigest": "D4A17F55545978FAEC6E8FA5E0056A7B",
immich_server  |   "LensProfileSetup": "Custom",
immich_server  |   "LensProfileName": "Adobe (Canon EF 70-200mm f/4 L IS USM)",
immich_server  |   "LensProfileFilename": "Canon EOS 5D Mark II (Canon EF 70-200mm f4L IS USM) - RAW.lcp",
immich_server  |   "LensProfileDigest": "68E67E44E81A8019F73740C8E4129BFC",
immich_server  |   "LensProfileIsEmbedded": false,
immich_server  |   "LensProfileDistortionScale": 100,
immich_server  |   "LensProfileVignettingScale": 0,
immich_server  |   "UprightVersion": 151388160,
immich_server  |   "UprightCenterMode": 0,
immich_server  |   "UprightCenterNormX": 0.5,
immich_server  |   "UprightCenterNormY": 0.5,
immich_server  |   "UprightFocalMode": 0,
immich_server  |   "UprightFocalLength35mm": 198.662795257,
immich_server  |   "UprightPreview": false,
immich_server  |   "UprightDependentDigest": "968015B5650CF3E77BE24AED5CE4A439",
immich_server  |   "UprightTransformCount": 6,
immich_server  |   "UprightTransform_0": "1.000000000,0.000000000,0.000000000,0.000000000,1.000000000,0.000000000,0.000000000,0.000000000,1.000000000",
immich_server  |   "UprightTransform_1": "1.003943149,-0.001773359,-0.001086698,0.003985624,1.003960003,-0.003974820,0.000000006,-0.000004011,1.000000000",
immich_server  |   "UprightTransform_2": "1.189074011,-0.000987887,-0.000000000,0.079750817,0.785272855,0.119638842,0.189041622,-0.001839434,1.000000000",
immich_server  |   "UprightTransform_3": "1.003963769,-0.001771390,-0.001096190,0.003985629,1.003963769,-0.003974699,0.000000000,0.000000000,1.000000000",
immich_server  |   "UprightTransform_4": "1.000011309,-0.000830801,-0.000006695,-0.000023384,0.999267957,-0.000463867,0.000000000,-0.001682377,1.000000000",
immich_server  |   "UprightTransform_5": "1.000000000,0.000000000,0.000000000,0.000000000,1.000000000,0.000000000,0.000000000,0.000000000,1.000000000",
immich_server  |   "AutoToneDigest": "E71E1226699CBE339D2A8268A2286DC9",
immich_server  |   "AutoToneDigestNoSat": "A3A269DB8F7F48F7DF63DD96ED5335BB",
immich_server  |   "HasSettings": true,
immich_server  |   "CropTop": 0,
immich_server  |   "CropLeft": 0,
immich_server  |   "CropBottom": 1,
immich_server  |   "CropRight": 1,
immich_server  |   "CropAngle": 0,
immich_server  |   "CropConstrainToWarp": 0,
immich_server  |   "HasCrop": false,
immich_server  |   "AlreadyApplied": true,
immich_server  |   "Rights": "Shot by Gaein nidb",
immich_server  |   "History": [
immich_server  |     {
immich_server  |       "Action": "saved",
immich_server  |       "Changed": "/metadata",
immich_server  |       "InstanceID": "xmp.iid:270ff50d-4e3b-c04f-a0c5-3f6fdf8a6be8",
immich_server  |       "SoftwareAgent": "Adobe Photoshop Camera Raw 16.3.1 (Windows)",
immich_server  |       "When": {
immich_server  |         "_ctor": "ExifDateTime",
immich_server  |         "year": 2024,
immich_server  |         "month": 8,
immich_server  |         "day": 20,
immich_server  |         "hour": 13,
immich_server  |         "minute": 10,
immich_server  |         "second": 4,
immich_server  |         "tzoffsetMinutes": 480,
immich_server  |         "rawValue": "2024:08:20 13:10:04+08:00",
immich_server  |         "zoneName": "UTC+8",
immich_server  |         "inferredZone": false
immich_server  |       }
immich_server  |     },
immich_server  |     {
immich_server  |       "Action": "derived",
immich_server  |       "Parameters": "converted from image/x-canon-cr2 to image/jxl, saved to new location"
immich_server  |     },
immich_server  |     {
immich_server  |       "Action": "saved",
immich_server  |       "Changed": "/",
immich_server  |       "InstanceID": "xmp.iid:629f4687-fd5e-3148-a2a0-16a238f8ffdb",
immich_server  |       "SoftwareAgent": "Adobe Photoshop Camera Raw 16.3.1 (Windows)",
immich_server  |       "When": {
immich_server  |         "_ctor": "ExifDateTime",
immich_server  |         "year": 2024,
immich_server  |         "month": 8,
immich_server  |         "day": 20,
immich_server  |         "hour": 15,
immich_server  |         "minute": 15,
immich_server  |         "second": 27,
immich_server  |         "tzoffsetMinutes": 480,
immich_server  |         "rawValue": "2024:08:20 15:15:27+08:00",
immich_server  |         "zoneName": "UTC+8",
immich_server  |         "inferredZone": false
immich_server  |       }
immich_server  |     }
immich_server  |   ],
immich_server  |   "DerivedFrom": {
immich_server  |     "DocumentID": "636A60734F7D67D33261FBA6084F3B59",
immich_server  |     "InstanceID": "xmp.iid:270ff50d-4e3b-c04f-a0c5-3f6fdf8a6be8",
immich_server  |     "OriginalDocumentID": "636A60734F7D67D33261FBA6084F3B59"
immich_server  |   },
immich_server  |   "ToneCurvePV2012": [
immich_server  |     "0, 0",
immich_server  |     "255, 255"
immich_server  |   ],
immich_server  |   "ToneCurvePV2012Red": [
immich_server  |     "0, 0",
immich_server  |     "255, 255"
immich_server  |   ],
immich_server  |   "ToneCurvePV2012Green": [
immich_server  |     "0, 0",
immich_server  |     "255, 255"
immich_server  |   ],
immich_server  |   "ToneCurvePV2012Blue": [
immich_server  |     "0, 0",
immich_server  |     "255, 255"
immich_server  |   ],
immich_server  |   "PointColors": [
immich_server  |     "-1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000, -1.000000"
immich_server  |   ],
immich_server  |   "Aperture": 9,
immich_server  |   "ImageSize": "5760x3840",
immich_server  |   "Megapixels": 22.1,
immich_server  |   "ScaleFactor35efl": 1,
immich_server  |   "ShutterSpeed": "1/100",
immich_server  |   "SubSecCreateDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 5,
immich_server  |     "day": 31,
immich_server  |     "hour": 10,
immich_server  |     "minute": 9,
immich_server  |     "second": 29,
immich_server  |     "millisecond": 200,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:05:31 10:09:29.20",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": true
immich_server  |   },
immich_server  |   "SubSecDateTimeOriginal": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 5,
immich_server  |     "day": 31,
immich_server  |     "hour": 10,
immich_server  |     "minute": 9,
immich_server  |     "second": 29,
immich_server  |     "millisecond": 200,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:05:31 10:09:29.20",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": true
immich_server  |   },
immich_server  |   "SubSecModifyDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 8,
immich_server  |     "day": 20,
immich_server  |     "hour": 15,
immich_server  |     "minute": 15,
immich_server  |     "second": 27,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:08:20 15:15:27+08:00",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": false
immich_server  |   },
immich_server  |   "Copyright": "Shot by Gaein nidb",
immich_server  |   "CreateDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 5,
immich_server  |     "day": 31,
immich_server  |     "hour": 10,
immich_server  |     "minute": 9,
immich_server  |     "second": 29,
immich_server  |     "millisecond": 200,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:05:31 10:09:29.20",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": true
immich_server  |   },
immich_server  |   "Creator": "Gaein nidb/gaein.cn",
immich_server  |   "DateTimeOriginal": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 5,
immich_server  |     "day": 31,
immich_server  |     "hour": 10,
immich_server  |     "minute": 9,
immich_server  |     "second": 29,
immich_server  |     "millisecond": 200,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:05:31 10:09:29.20",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": true
immich_server  |   },
immich_server  |   "ModifyDate": {
immich_server  |     "_ctor": "ExifDateTime",
immich_server  |     "year": 2024,
immich_server  |     "month": 8,
immich_server  |     "day": 20,
immich_server  |     "hour": 15,
immich_server  |     "minute": 15,
immich_server  |     "second": 27,
immich_server  |     "tzoffsetMinutes": 480,
immich_server  |     "rawValue": "2024:08:20 15:15:27+08:00",
immich_server  |     "zoneName": "UTC+8",
immich_server  |     "inferredZone": false
immich_server  |   },
immich_server  |   "CircleOfConfusion": "0.030 mm",
immich_server  |   "DOF": "inf (147.92 m - inf)",
immich_server  |   "FOV": "10.3 deg",
immich_server  |   "FocalLength35efl": "200.0 mm (35 mm equivalent: 200.0 mm)",
immich_server  |   "HyperfocalDistance": "147.92 m",
immich_server  |   "LightValue": 14,
immich_server  |   "LensID": "Canon EF 70-200mm f/4L IS USM",
immich_server  |   "warnings": []
immich_server  | }
immich_server  |
immich_server  | [Nest] 7  - 08/20/2024, 4:40:52 PM   DEBUG [Microservices:MediaService] Attempting to rename file: upload/upload/a085767f-89b2-4f0a-94b0-72c819103657/0c/4b/0c4b6997-f49e-4f57-aaf7-46192c975fe5.JXL => upload/library/admin/2024-05-31/_5D34943_loss.JXL
immich_server  | ./lib/jxl/modular/transform/transform.h:86: JXL_FAILURE: Invalid transform ID
immich_server  | ./lib/jxl/fields.cc:619: JXL_RETURN_IF_ERROR code=1: visitor.Visit(fields)
immich_server  | ./lib/jxl/modular/encoding/encoding.cc:458: JXL_RETURN_IF_ERROR code=1: status
immich_server  | ./lib/jxl/modular/encoding/encoding.cc:597: JXL_RETURN_IF_ERROR code=1: dec_status
immich_server  | ./lib/jxl/dec_modular.cc:413: JXL_FAILURE: Failed to decode modular DC group
immich_server  | ./lib/jxl/dec_frame.cc:308: JXL_RETURN_IF_ERROR code=1: modular_frame_decoder_.DecodeVarDCTDC(dc_group_id, br, dec_state_)
immich_server  | ./lib/jxl/dec_frame.cc:644: JXL_FAILURE: Error in DC group
immich_server  | ./lib/jxl/decode.cc:1182: frame processing failed
immich_server  | [Nest] 7  - 08/20/2024, 4:40:52 PM   ERROR [Microservices:JobService] Unable to run job handler (thumbnailGeneration/generate-preview): Error: jxlload: error JxlDecoderProcessInput
immich_server  | [Nest] 7  - 08/20/2024, 4:40:52 PM   ERROR [Microservices:JobService] Error: jxlload: error JxlDecoderProcessInput
immich_server  |     at Sharp.toFile (/usr/src/app/node_modules/sharp/lib/output.js:89:19)
immich_server  |     at MediaRepository.generateThumbnail (/usr/src/app/dist/repositories/media.repository.js:69:14)
immich_server  |     at MediaService.generateThumbnail (/usr/src/app/dist/services/media.service.js:164:48)
immich_server  |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
immich_server  |     at async MediaService.handleGeneratePreview (/usr/src/app/dist/services/media.service.js:135:29)
immich_server  |     at async /usr/src/app/dist/services/job.service.js:148:36
immich_server  |     at async Worker.processJob (/usr/src/app/node_modules/bullmq/dist/cjs/classes/worker.js:394:28)
immich_server  |     at async Worker.retryIfFailed (/usr/src/app/node_modules/bullmq/dist/cjs/classes/worker.js:581:24)
immich_server  | [Nest] 7  - 08/20/2024, 4:40:52 PM   ERROR [Microservices:JobService] Object:
immich_server  | {
immich_server  |   "id": "7aa9a7f1-e7a0-4b98-be40-5def7ba3832b",
immich_server  |   "source": "upload"
immich_server  | }

Additional information

No response

nidbCN commented 2 weeks ago

I decided to do something with the source code and to find the reason or provide some clues. In the log I find the output from libjxl said 'JXL_FAILURE: Invalid transform ID', it seems happend during handleGeneratePreview and logs before the function throw exceptions, so I go the source code to find which function called the libjxl to decode. After view the source code and log message I noticed that after upload, immich use mediaRepository.generateThumbnail and this function call sharp.toFile, but the library sharp DOSE NOT SUPPORT JXL files. But I still can't find which function called libjxl and what arguments were passed. Then I add a console.log to source code of sharp and re-generate preview, I found JXL_FAILURE: Invalid transform ID happen after sharp, I'll check the source code of library sharp. It seems the problem is not from immich

nidbCN commented 1 week ago

UPDATE: use libjxl 0.9 and rebuild docker images solve the problem. I built a image for personally useage: dockerhub

I can't verify if the problem cause by sharp and not by immich. It require me build my own libvips and I don't know what's in immich's version. For temporary use and experiment, I write a patch and try use ffmpeg to convert jxl instead of sharp. I share it here maybe it will be helpful for someone. But it not a good solution and I'll continuing find why JXLDecode failed. **NOTICE: THIS SOLUTION IS ONLY FOR EXPERIMENT AND NOT USE THIS IN PRODUCTION ENVIRONMENT** ./patch/patch.js:

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
  return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
  return (mod && mod.__esModule) ? mod : { "default": mod };
};
var MediaRepository_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MediaRepository = void 0;

// start patch
const process = require("child_process");
// end patch

const common_1 = require("@nestjs/common");
const exiftool_vendored_1 = require("exiftool-vendored");
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
const promises_1 = __importDefault(require("node:fs/promises"));
const node_util_1 = require("node:util");
const sharp_1 = __importDefault(require("sharp"));
const config_1 = require("../config");
const logger_interface_1 = require("../interfaces/logger.interface");
const instrumentation_1 = require("../utils/instrumentation");
const misc_1 = require("../utils/misc");
const probe = (0, node_util_1.promisify)(fluent_ffmpeg_1.default.ffprobe);
sharp_1.default.concurrency(0);
sharp_1.default.cache({ files: 0 });
let MediaRepository = MediaRepository_1 = class MediaRepository {
  logger;
  constructor(logger) {
    this.logger = logger;
    this.logger.setContext(MediaRepository_1.name);
  }
  async extract(input, output) {
    try {
      await exiftool_vendored_1.exiftool.extractJpgFromRaw(input, output);
    }
    catch (error) {
      this.logger.debug('Could not extract JPEG from image, trying preview', error.message);
      try {
        await exiftool_vendored_1.exiftool.extractPreview(input, output);
      }
      catch (error) {
        this.logger.debug('Could not extract preview from image', error.message);
        return false;
      }
    }
    return true;
  }
  async generateThumbnail(input, output, options) {
    // start patch
    if (input.toLowerCase().endsWith(".jxl") && options.format == "webp") {
        console.warn("Running patch script for jxl.");
        const { stdout, stderr } = await process.exec(`/usr/src/app/jxl_ffmpeg -hide_banner -i "/usr/src/app/${input}" -vf scale="-1:${options.size}" -quality ${options.quality} "/usr/src/app/${output}"`);

        if (!stderr) {
          console.info("Patch run success, jxl has convert to webp.");
        } else {
          console.error(stderr)
        }

      //./jxl_ffmpeg -i upload/library/a085767f-89b2-4f0a-94b0-72c819103657/2024-09-04/ffmpeg.jxl -vf scale="-1:1440" -quality 80 upload/thumbs/a085767f-89b2-4f0a-94b0-72c819103657/fa/63/fa639e52-a8c1-468e-aa63-089cf9c7c733-preview.webp
    } else {
      // end patch
      const pipeline = (0, sharp_1.default)(input, { failOn: options.processInvalidImages ? 'none' : 'error', limitInputPixels: false })
        .pipelineColorspace(options.colorspace === config_1.Colorspace.SRGB ? 'srgb' : 'rgb16')
        .rotate();
      if (options.crop) {
        pipeline.extract(options.crop);
      }
      await pipeline
        .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
        .withIccProfile(options.colorspace)
        .toFormat(options.format, {
          quality: options.quality,
          chromaSubsampling: options.quality >= 80 ? '4:4:4' : '4:2:0',
        })
        .toFile(output);
    }
    // start patch
  }
  // end patch
  async probe(input) {
    const results = await probe(input);
    return {
      format: {
        formatName: results.format.format_name,
        formatLongName: results.format.format_long_name,
        duration: results.format.duration || 0,
        bitrate: results.format.bit_rate ?? 0,
      },
      videoStreams: results.streams
        .filter((stream) => stream.codec_type === 'video')
        .filter((stream) => !stream.disposition?.attached_pic)
        .map((stream) => ({
          index: stream.index,
          height: stream.height || 0,
          width: stream.width || 0,
          codecName: stream.codec_name === 'h265' ? 'hevc' : stream.codec_name,
          codecType: stream.codec_type,
          frameCount: Number.parseInt(stream.nb_frames ?? '0'),
          rotation: Number.parseInt(`${stream.rotation ?? 0}`),
          isHDR: stream.color_transfer === 'smpte2084' || stream.color_transfer === 'arib-std-b67',
          bitrate: Number.parseInt(stream.bit_rate ?? '0'),
        })),
      audioStreams: results.streams
        .filter((stream) => stream.codec_type === 'audio')
        .map((stream) => ({
          index: stream.index,
          codecType: stream.codec_type,
          codecName: stream.codec_name,
          frameCount: Number.parseInt(stream.nb_frames ?? '0'),
        })),
    };
  }
  transcode(input, output, options) {
    if (!options.twoPass) {
      return new Promise((resolve, reject) => {
        this.configureFfmpegCall(input, output, options)
          .on('error', reject)
          .on('end', () => resolve())
          .run();
      });
    }
    if (typeof output !== 'string') {
      throw new TypeError('Two-pass transcoding does not support writing to a stream');
    }
    return new Promise((resolve, reject) => {
      this.configureFfmpegCall(input, '/dev/null', options)
        .addOptions('-pass', '1')
        .addOptions('-passlogfile', output)
        .addOptions('-f null')
        .on('error', reject)
        .on('end', () => {
          this.configureFfmpegCall(input, output, options)
            .addOptions('-pass', '2')
            .addOptions('-passlogfile', output)
            .on('error', reject)
            .on('end', () => (0, misc_1.handlePromiseError)(promises_1.default.unlink(`${output}-0.log`), this.logger))
            .on('end', () => (0, misc_1.handlePromiseError)(promises_1.default.rm(`${output}-0.log.mbtree`, { force: true }), this.logger))
            .on('end', () => resolve())
            .run();
        })
        .run();
    });
  }
  async generateThumbhash(imagePath) {
          console.log(imagePath);
    const maxSize = 100;
    const { data, info } = await (0, sharp_1.default)(imagePath)
      .resize(maxSize, maxSize, { fit: 'inside', withoutEnlargement: true })
      .raw()
      .ensureAlpha()
      .toBuffer({ resolveWithObject: true });
    const thumbhash = await import('thumbhash');
    return Buffer.from(thumbhash.rgbaToThumbHash(info.width, info.height, data));
  }
  async getImageDimensions(input) {
    const { width = 0, height = 0 } = await (0, sharp_1.default)(input).metadata();
    return { width, height };
  }
  configureFfmpegCall(input, output, options) {
    return (0, fluent_ffmpeg_1.default)(input, { niceness: 10 })
      .inputOptions(options.inputOptions)
      .outputOptions(options.outputOptions)
      .output(output)
      .on('error', (error, stdout, stderr) => this.logger.error(stderr || error));
  }
};
exports.MediaRepository = MediaRepository;
exports.MediaRepository = MediaRepository = MediaRepository_1 = __decorate([
  (0, instrumentation_1.Instrumentation)(),
  (0, common_1.Injectable)(),
  __param(0, (0, common_1.Inject)(logger_interface_1.ILoggerRepository)),
  __metadata("design:paramtypes", [Object])
], MediaRepository);

docker-compose.yml

# ...
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    extends:
      file: hwaccel.transcoding.yml
      service: quicksync # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
    volumes:
      # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
      - ./patch/ffmpeg:/usr/src/app/jxl_ffmpeg
      - ./patch/patch.js:/usr/src/app/dist/repositories/media.repository.js
    env_file:
      - .env
# ...

and put a ffmpeg bin that support libjxl and libwebp to path folder.

mertalev commented 1 week ago

The version of libjxl in the container is probably too old (v0.7.0) for these JXL files.

nidbCN commented 1 week ago

The version of libjxl in the container is probably too old (v0.7.0) for these JXL files.

I use the libjxl offical tools djxl(v0.7.0) and it can correct decode the jxl files to png.

If there's another function that cause the decode failed, what should I do to upgrade the libjxl? Thanks a lot.

mertalev commented 1 week ago

Hmm, interesting that it worked with djxl 0.7.0. I think that makes it more likely that it's a libvips issue. It's also possible that libvips is only tested against a newer version of libjxl. You can make an issue about it and provide a sample image to see what they suggest.

mertalev commented 1 week ago

Other than that, if you'd like to try building immich and libvips with a newer version of libjxl:

  1. Clone the immich and base-images repos and follow the setup instructions (except that you don't need to start the server yet)
  2. In the base image Dockerfile, move libjxl-dev and libjxl0.7 to the unstable section and change libjxl0.7 to libjxl0.9
  3. Build the image with docker build . -t base-dev --target=dev and docker build . -t base-prod
  4. In the server Dockerfile, change the dev and prod images to extend from base-dev and base-prod
  5. Run either make dev-update or make prod in the root immich folder to bring the server up
  6. Try uploading a JXL image to this immich instance
nidbCN commented 1 week ago

Other than that, if you'd like to try building immich and libvips with a newer version of libjxl:

  1. Clone the immich and base-images repos and follow the setup instructions (except that you don't need to start the server yet)
  2. In the base image Dockerfile, move libjxl-dev and libjxl0.7 to the unstable section and change libjxl0.7 to libjxl0.9
  3. Build the image with docker build . -t base-dev --target=dev and docker build . -t base-prod
  4. In the server Dockerfile, change the dev and prod images to base-dev and base-prod.
  5. Run either make dev-update or make prod to bring the server up
  6. Try uploading a JXL image to this immich instance

Ok I'll try that. I personally has try built a libvips of new version but I can't confirm which the libvips that immich use. Now according your reply it seems I just need to change the new libjxl.

Thanks for your help.

nidbCN commented 1 week ago

Other than that, if you'd like to try building immich and libvips with a newer version of libjxl:

  1. Clone the immich and base-images repos and follow the setup instructions (except that you don't need to start the server yet)
  2. In the base image Dockerfile, move libjxl-dev and libjxl0.7 to the unstable section and change libjxl0.7 to libjxl0.9
  3. Build the image with docker build . -t base-dev --target=dev and docker build . -t base-prod
  4. In the server Dockerfile, change the dev and prod images to extend from base-dev and base-prod
  5. Run either make dev-update or make prod in the root immich folder to bring the server up
  6. Try uploading a JXL image to this immich instance

After upgrade libjxl to 0.9 and re-build, immich can successfully generate preview for jxl images export by Adobe ACR.

The images in this table all can be process correctly.

exif qulity color deepth
all lossless 16bit
copyright only 80 16bit
copyright only 80 8bit

One more question. I'd like to know why immich offical use libjxl 0.7 and not upgrade to 0.9, is there any stability issue?

Thanks for your help a lot.

mertalev commented 1 week ago

Glad to hear it's working! There's no particular stability reason for using 0.7.0 - it's just Debian stable installs. We can change this in the base image.

nidbCN commented 1 week ago

Glad to hear it's working! There's no particular stability reason for using 0.7.0 - it's just Debian stable installs. We can change this in the base image.

Should I create a pull request in base image repo or just wait immich's next version?

mertalev commented 1 week ago

Feel free to make a PR for it! Except that it should use testing instead of unstable (latest commit in main adds testing).

nidbCN commented 1 week ago

Feel free to make a PR for it! Except that it should use testing instead of unstable (latest commit in main adds testing).

copy that, thanks for your help again