[BUG] Live photos correctly display in Chrome but display as individual photos and videos in iOS Client #1955

Closed
opened 2026-02-05 04:35:19 +03:00 by OVERLORD · 14 comments
Owner

Originally created by @AngelPone on GitHub (Jan 8, 2024).

The bug

The Live Photos in my library are correctly identified and displayed in Chrome, but display as individual photos and videos in iOS Client.

The OS that Immich Server is running on

Ubuntu 22.10

Version of Immich Server

v1.91.4

Version of Immich Mobile App

v1.91.4 build.132

Platform with the issue

  • Server
  • Web
  • Mobile

Your docker-compose.yml content

version: "3.8"
services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    command: [ "start.sh", "immich" ]
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - 2451:3001
    depends_on:
      - redis
    restart: always

  immich-microservices:
    container_name: immich_microservices
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    command: [ "start.sh", "microservices" ]
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    depends_on:
      - redis
    restart: always
immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always
  redis:
    container_name: immich_redis
    image: redis:6.2-alpine@sha256:c5a607fb6e1bb15d32bbcf14db22787d19e428d59e31a5da67511b49bb0f1ccc
    restart: always

volumes:
  model-cache:

Your .env content

DB_HOSTNAME=192.168.50.100
DB_USERNAME=immich
DB_PASSWORD=immich
DB_DATABASE_NAME=immich

# Optional Database settings:
DB_PORT=5432
REDIS_HOSTNAME=immich_redis
UPLOAD_LOCATION=/path/to/location
PORT=3000
SERVER_PORT=3001
MICROSERVICES_PORT=3002
MACHINE_LEARNING_HOST=0.0.0.0
MACHINE_LEARNING_PORT=3003
IMMICH_VERSION=release

Reproduction steps

1. I disabled the access to photo library of immich app. Thus, the app only displays Photos uploaded in the cloud.
2. The live photos are uploaded into the library through immich cli.
2. All Live photos are correctly displayed in Chrome, but are shown as individual photos and videos in iOS app.

Additional information

No response

Originally created by @AngelPone on GitHub (Jan 8, 2024). ### The bug The Live Photos in my library are correctly identified and displayed in Chrome, but display as individual photos and videos in iOS Client. ### The OS that Immich Server is running on Ubuntu 22.10 ### Version of Immich Server v1.91.4 ### Version of Immich Mobile App v1.91.4 build.132 ### Platform with the issue - [ ] Server - [ ] Web - [X] Mobile ### Your docker-compose.yml content ```YAML version: "3.8" services: immich-server: container_name: immich_server image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} command: [ "start.sh", "immich" ] volumes: - ${UPLOAD_LOCATION}:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro env_file: - .env ports: - 2451:3001 depends_on: - redis restart: always immich-microservices: container_name: immich_microservices image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} command: [ "start.sh", "microservices" ] volumes: - ${UPLOAD_LOCATION}:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro env_file: - .env depends_on: - redis restart: always immich-machine-learning: container_name: immich_machine_learning image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} volumes: - model-cache:/cache env_file: - .env restart: always redis: container_name: immich_redis image: redis:6.2-alpine@sha256:c5a607fb6e1bb15d32bbcf14db22787d19e428d59e31a5da67511b49bb0f1ccc restart: always volumes: model-cache: ``` ### Your .env content ```Shell DB_HOSTNAME=192.168.50.100 DB_USERNAME=immich DB_PASSWORD=immich DB_DATABASE_NAME=immich # Optional Database settings: DB_PORT=5432 REDIS_HOSTNAME=immich_redis UPLOAD_LOCATION=/path/to/location PORT=3000 SERVER_PORT=3001 MICROSERVICES_PORT=3002 MACHINE_LEARNING_HOST=0.0.0.0 MACHINE_LEARNING_PORT=3003 IMMICH_VERSION=release ``` ### Reproduction steps ```bash 1. I disabled the access to photo library of immich app. Thus, the app only displays Photos uploaded in the cloud. 2. The live photos are uploaded into the library through immich cli. 2. All Live photos are correctly displayed in Chrome, but are shown as individual photos and videos in iOS app. ``` ### Additional information _No response_
Author
Owner

@ndewijer commented on GitHub (Jan 9, 2024):

I have the same issue. (iphone 13, 1.92.1 server/client) But I use firefox on MacOS. Attached screenshots.

Screenshot 2024-01-09 at 15 26 03

IMG_2812

Files were uploaded via the CLI.

@ndewijer commented on GitHub (Jan 9, 2024): I have the same issue. (iphone 13, 1.92.1 server/client) But I use firefox on MacOS. Attached screenshots. ![Screenshot 2024-01-09 at 15 26 03](https://github.com/immich-app/immich/assets/34378210/69b32220-16ec-4069-8ce6-c749702e826a) ![IMG_2812](https://github.com/immich-app/immich/assets/34378210/2fb2c30f-f355-432a-816e-ecb305be8b59) Files were uploaded via the CLI.
Author
Owner

@alextran1502 commented on GitHub (Jan 9, 2024):

@AngelPone how did you upload those LivePhotos, is it from the mobile app upload or from the CLI?

@alextran1502 commented on GitHub (Jan 9, 2024): @AngelPone how did you upload those LivePhotos, is it from the mobile app upload or from the CLI?
Author
Owner

@AngelPone commented on GitHub (Jan 9, 2024):

@AngelPone how did you upload those LivePhotos, is it from the mobile app upload or from the CLI?

from the CLI @alextran1502

@AngelPone commented on GitHub (Jan 9, 2024): > @AngelPone how did you upload those LivePhotos, is it from the mobile app upload or from the CLI? > from the CLI @alextran1502
Author
Owner

@alextran1502 commented on GitHub (Jan 9, 2024):

@ndewijer @AngelPone Those LivePhotos are probably edited. Once LivePhotos is edited, it is saved under a different "link" ID, so we don't have any trace to link it back if you are uploading using the CLI, since the two files are uploaded individually.

Uploading from the mobile app, the payload contains both the motion and the still parts, so they will always linked correctly.

This is how Apple handles editing LivePhotos and I don't think there is anything we can do here

@alextran1502 commented on GitHub (Jan 9, 2024): @ndewijer @AngelPone Those LivePhotos are probably edited. Once LivePhotos is edited, it is saved under a different "link" ID, so we don't have any trace to link it back if you are uploading using the CLI, since the two files are uploaded individually. Uploading from the mobile app, the payload contains both the motion and the still parts, so they will always linked correctly. This is how Apple handles editing LivePhotos and I don't think there is anything we can do here
Author
Owner

@ndewijer commented on GitHub (Jan 9, 2024):

@ndewijer @AngelPone Those LivePhotos are probably edited. Once LivePhotos is edited, it is saved under a different "link" ID, so we don't have any trace to link it back if you are uploading using the CLI, since the two files are uploaded individually.

Uploading from the mobile app, the payload contains both the motion and the still parts, so they will always linked correctly.

This is how Apple handles editing LivePhotos and I don't think there is anything we can do here

Hi @alextran1502 I fully hear what you’re saying, but why are they merged when viewed via the browser, though? That would imply to me they are linked.

I did just check the pictures on my iPhone, and they are not edited. As you say, when you edit them, the live photo gets removed.

IMG_2813

@ndewijer commented on GitHub (Jan 9, 2024): > @ndewijer @AngelPone Those LivePhotos are probably edited. Once LivePhotos is edited, it is saved under a different "link" ID, so we don't have any trace to link it back if you are uploading using the CLI, since the two files are uploaded individually. > > Uploading from the mobile app, the payload contains both the motion and the still parts, so they will always linked correctly. > > This is how Apple handles editing LivePhotos and I don't think there is anything we can do here Hi @alextran1502 I fully hear what you’re saying, but why are they merged when viewed via the browser, though? That would imply to me they are linked. I did just check the pictures on my iPhone, and they are not edited. As you say, when you edit them, the live photo gets removed. ![IMG_2813](https://github.com/immich-app/immich/assets/34378210/d3915547-3652-440f-a2dd-6541e3d66895)
Author
Owner

@alextran1502 commented on GitHub (Jan 9, 2024):

@ndewijer Ah your case might be different. Can you try logout and log back in?

@alextran1502 commented on GitHub (Jan 9, 2024): @ndewijer Ah your case might be different. Can you try logout and log back in?
Author
Owner

@ndewijer commented on GitHub (Jan 9, 2024):

@ndewijer Ah your case might be different. Can you try logout and log back in?

Done, no effect. I can also do you one better. We see the same effect on my wife’s phone which is a Pixel 7a with Android 13 which is fully up to date.

18683b94-ff04-47d8-9965-15c7d7e78021

@ndewijer commented on GitHub (Jan 9, 2024): > @ndewijer Ah your case might be different. Can you try logout and log back in? Done, no effect. I can also do you one better. We see the same effect on my wife’s phone which is a Pixel 7a with Android 13 which is fully up to date. ![18683b94-ff04-47d8-9965-15c7d7e78021](https://github.com/immich-app/immich/assets/34378210/48d4a497-506b-46d7-bac0-5945851de111)
Author
Owner

@AngelPone commented on GitHub (Jan 9, 2024):

Same here. The pictures are original. Actually, none of the live photos correctly display on my phone.

I also tried to reinstall the app and didn't work.

@AngelPone commented on GitHub (Jan 9, 2024): Same here. The pictures are original. Actually, none of the live photos correctly display on my phone. I also tried to reinstall the app and didn't work.
Author
Owner

@alextran1502 commented on GitHub (Jan 9, 2024):

@AngelPone Do you mind share one set of example for troubleshooting?

@alextran1502 commented on GitHub (Jan 9, 2024): @AngelPone Do you mind share one set of example for troubleshooting?
Author
Owner

@AngelPone commented on GitHub (Jan 10, 2024):

@alextran1502
The original photo: https://1drv.ms/f/s!AsbsRMJoB2Ryi9hrGZlZHBr4oIYSfg?e=7pdfay
The view on Chrome:
CleanShot 2024-01-10 at 11 26 27
The View on iPhone:
IMG_7811

@AngelPone commented on GitHub (Jan 10, 2024): @alextran1502 The original photo: https://1drv.ms/f/s!AsbsRMJoB2Ryi9hrGZlZHBr4oIYSfg?e=7pdfay The view on Chrome: ![CleanShot 2024-01-10 at 11 26 27](https://github.com/immich-app/immich/assets/32930283/7f590bbb-25b6-4701-9d20-75b5bafaed19) The View on iPhone: ![IMG_7811](https://github.com/immich-app/immich/assets/32930283/0a500adc-36e6-47f6-8e65-3f7b46594361)
Author
Owner

@cipisek9 commented on GitHub (Jan 17, 2024):

Hi, I have the same problem.
Initially, I thought that the photo and video are displayed separately, but I have noticed that I have a play button and I can play the video while looking at the photo.

Snímka obrazovky 2024-01-17 o 10 29 03

However, the video from this photo is also shown separately. So it seems that the photo+video is displayed correctly, and the same video is displayed separately once more.

Of course I have this problem only in iOS app. In the browser it is ok. It doesn't matter if I upload it trough web browser or directly from iOS. There is same problem. I am using latest version of server and iOS. Running on Raspberry Pi 5.

Is there any solution how to fix this?

@cipisek9 commented on GitHub (Jan 17, 2024): Hi, I have the same problem. Initially, I thought that the photo and video are displayed separately, but I have noticed that I have a play button and I can play the video while looking at the photo. ![Snímka obrazovky 2024-01-17 o 10 29 03](https://github.com/immich-app/immich/assets/154505089/fcd4bb41-cefc-40a6-9ea6-6792ca42f46e) However, the video from this photo is also shown separately. So it seems that the photo+video is displayed correctly, and the same video is displayed separately once more. Of course I have this problem only in iOS app. In the browser it is ok. It doesn't matter if I upload it trough web browser or directly from iOS. There is same problem. I am using latest version of server and iOS. Running on Raspberry Pi 5. Is there any solution how to fix this?
Author
Owner

@AngelPone commented on GitHub (Mar 1, 2024):

After digging into this, I figured out the problem.
First note that this problem only happens for part of my previous photos. Recent photos don't have this problem.

In my immich, there are two sources that make this problem happen:

  1. the live photos are not identified as live photos (livePhotoVideoId is empty in the database)
  2. the live photo is identified as live photo, but the video part (.mov) was accidently added into an album when the image part was added into the album. (which I think should not?)
    For example, if I add a live photo into an album and this problem happen, call the /album/:albumId api will return 2 items in the assets field of the response (which I think should be 1?).

Why it causes the bug view on Mobile?
Mobile code calls the album API to update album. For the second case, the api returns two items(original asset in image + video) for the same asset. But the mobile client does not check it, and upserts them into the database. This exactly causes the problem @cipisek9 and I have encountered. This also explains the two wierd things I encountered:

  1. When I first open the app, the live videos actually do not show up in the timeline. But after immich finishs querying the album in background, they show up in the time line.
  2. The item count for the album on the Web client is bigger than number of items actually showing up in the album view.

The only thing I can't understand is why Web client does not display these motion videos as they are using the same api.

Here is how I fix it

  1. For the first case, I update the liveVideoPhotoId field of the image asset of the live photos by directly updating the database.
  2. For the second case, I first filtered out all the VIDEO part of live photos and check it if added into the albums_assets_assets. Then I call the removeAssetFromAlbum api to remove these live VIDEOs from the album.
@AngelPone commented on GitHub (Mar 1, 2024): After digging into this, I figured out the problem. First note that this problem only happens for part of my previous photos. Recent photos don't have this problem. In my immich, there are two sources that make this problem happen: 1. the live photos are not identified as live photos (livePhotoVideoId is empty in the database) 2. the live photo is identified as live photo, but the video part (.mov) was accidently added into an album when the image part was added into the album. (which I think should not?) For example, if I add a live photo into an album and this problem happen, call the /album/:albumId api will return 2 items in the assets field of the response (which I think should be 1?). **Why it causes the bug view on Mobile?** Mobile code calls the album API to update album. For the second case, the api returns two items(original asset in image + video) for the same asset. But the mobile client does not check it, and upserts them into the database. This exactly causes the problem @cipisek9 and I have encountered. This also explains the two wierd things I encountered: 1. When I first open the app, the live videos actually do not show up in the timeline. But after immich finishs querying the album in background, they show up in the time line. 2. The item count for the album on the Web client is bigger than number of items actually showing up in the album view. The only thing I can't understand is why Web client does not display these motion videos as they are using the same api. **Here is how I fix it** 1. For the first case, I update the liveVideoPhotoId field of the image asset of the live photos by directly updating the database. 2. For the second case, I first filtered out all the VIDEO part of live photos and check it if added into the albums_assets_assets. Then I call the removeAssetFromAlbum api to remove these live VIDEOs from the album.
Author
Owner

@ndewijer commented on GitHub (Mar 1, 2024):

@AngelPone Mind sharing what queries you ran against postgres and against the API? I'm not afraid to get my hands dirty but I'd prefer not to reinvent the wheel

@ndewijer commented on GitHub (Mar 1, 2024): @AngelPone Mind sharing what queries you ran against postgres and against the API? I'm not afraid to get my hands dirty but I'd prefer not to reinvent the wheel
Author
Owner

@AngelPone commented on GitHub (Mar 1, 2024):

Here is the python code I wrote, ugly but work for me.
Use it in caution! @ndewijer

import requests
import pandas as pd
import numpy as np
from sqlalchemy import create_engine
from sqlalchemy.sql import text

ownerId = "your-id"
api_endpoint = "http://immich_api:port/api"
api_key = 'your api key'

# Create an engine instance
engine = create_engine(f'postgresql://immich:immich@databaseip/immich')


def get_album_id(album_name):
    return str(pd.read_sql(f"""
select * from albums where "albumName" = '{album_name}' and "ownerId" = '{ownerId}'
""",engine)["id"][0])


def remove_redundent_motion_video(album_name):
    album_id = get_album_id(album_name)
    album_assets = pd.read_sql(f"""
select * from albums_assets_assets where "albumsId" = '{album_id}'
""", engine)
    print(f"{album_assets.shape[0]} items in Album")

    allAssets = pd.read_sql(f"""
select * from assets where "ownerId" = '{ownerId}'
""", engine)
    allAssets.rename(columns={"id": "assetsId"}, inplace=True)

    merged = pd.merge(album_assets, allAssets, how="left", on="assetsId")
    merged = merged.loc[merged["deletedAt"].isna(),]
    print(f"{merged.shape[0]} is not deleted")
    print(f"{merged['type'].isna().sum()} assets missing in assets table")

    print("updating Live Photos without livePhotoVideoID")

    count = 0
    for i in merged["originalFileName"].unique():
        if i is np.nan:
            continue
        if sum(merged["originalFileName"] == i)  == 1:
            continue
        else:
            assert sum(merged["originalFileName"] == i)  == 2
            currentAsset = allAssets.loc[allAssets["originalFileName"] == i,]
            try:
                imageId = currentAsset.loc[currentAsset["type"] == "IMAGE",]["assetsId"].tolist()[0]
                videoId = currentAsset.loc[currentAsset["type"] == "VIDEO",]["assetsId"].tolist()[0]
                livePhotoId = currentAsset.loc[currentAsset["type"] == "IMAGE",]["livePhotoVideoId"].tolist()[0]
                if livePhotoId is None:
                    count += 1
                    SQL3 = text(f"""
UPDATE assets 
SET "livePhotoVideoId" = '{videoId}'
where "id" = '{imageId}'
    """)
                    with engine.connect() as connection:
                        result = connection.execute(SQL3)
                        connection.commit()
            except:
                continue
    print(f"updating {count} assets")
    
    merged_motion = merged.loc[~merged["livePhotoVideoId"].isna(),]["livePhotoVideoId"].tolist()
    merged_motion = [str(i) for i in merged_motion]
    print(f"{len(merged_motion)} live photos")
    allIds = [str(i) for i in merged["assetsId"]]
    merged_motion_in_album = list(filter(lambda x: x in allIds, merged_motion))
    print(f"{len(merged_motion_in_album)} videos of live photo in the album")
    print(f"There should be {merged.shape[0] - len(merged_motion_in_album) - merged['type'].isna().sum()} items in the album. Check it in your web")
    
    print("removing Live VIDEOS from the album")
    requests.delete(f"{api_endpoint}/album/{album_id}/assets", json={ "ids": merged_motion_in_album}, headers = {
        "Content-Type": "application/json",
        'x-api-key': api_key
    })

if __name__ == "__main__":
    remove_redundent_motion_video("album name") 
@AngelPone commented on GitHub (Mar 1, 2024): Here is the python code I wrote, ugly but work for me. Use it in caution! @ndewijer ```py import requests import pandas as pd import numpy as np from sqlalchemy import create_engine from sqlalchemy.sql import text ownerId = "your-id" api_endpoint = "http://immich_api:port/api" api_key = 'your api key' # Create an engine instance engine = create_engine(f'postgresql://immich:immich@databaseip/immich') def get_album_id(album_name): return str(pd.read_sql(f""" select * from albums where "albumName" = '{album_name}' and "ownerId" = '{ownerId}' """,engine)["id"][0]) def remove_redundent_motion_video(album_name): album_id = get_album_id(album_name) album_assets = pd.read_sql(f""" select * from albums_assets_assets where "albumsId" = '{album_id}' """, engine) print(f"{album_assets.shape[0]} items in Album") allAssets = pd.read_sql(f""" select * from assets where "ownerId" = '{ownerId}' """, engine) allAssets.rename(columns={"id": "assetsId"}, inplace=True) merged = pd.merge(album_assets, allAssets, how="left", on="assetsId") merged = merged.loc[merged["deletedAt"].isna(),] print(f"{merged.shape[0]} is not deleted") print(f"{merged['type'].isna().sum()} assets missing in assets table") print("updating Live Photos without livePhotoVideoID") count = 0 for i in merged["originalFileName"].unique(): if i is np.nan: continue if sum(merged["originalFileName"] == i) == 1: continue else: assert sum(merged["originalFileName"] == i) == 2 currentAsset = allAssets.loc[allAssets["originalFileName"] == i,] try: imageId = currentAsset.loc[currentAsset["type"] == "IMAGE",]["assetsId"].tolist()[0] videoId = currentAsset.loc[currentAsset["type"] == "VIDEO",]["assetsId"].tolist()[0] livePhotoId = currentAsset.loc[currentAsset["type"] == "IMAGE",]["livePhotoVideoId"].tolist()[0] if livePhotoId is None: count += 1 SQL3 = text(f""" UPDATE assets SET "livePhotoVideoId" = '{videoId}' where "id" = '{imageId}' """) with engine.connect() as connection: result = connection.execute(SQL3) connection.commit() except: continue print(f"updating {count} assets") merged_motion = merged.loc[~merged["livePhotoVideoId"].isna(),]["livePhotoVideoId"].tolist() merged_motion = [str(i) for i in merged_motion] print(f"{len(merged_motion)} live photos") allIds = [str(i) for i in merged["assetsId"]] merged_motion_in_album = list(filter(lambda x: x in allIds, merged_motion)) print(f"{len(merged_motion_in_album)} videos of live photo in the album") print(f"There should be {merged.shape[0] - len(merged_motion_in_album) - merged['type'].isna().sum()} items in the album. Check it in your web") print("removing Live VIDEOS from the album") requests.delete(f"{api_endpoint}/album/{album_id}/assets", json={ "ids": merged_motion_in_album}, headers = { "Content-Type": "application/json", 'x-api-key': api_key }) if __name__ == "__main__": remove_redundent_motion_video("album name") ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: immich-app/immich#1955