[TIL] iPhone Live Photo

2026. 05. 08.Yeji Kim
Frontend

들어가며

iPhone에서 사진을 찍으면 정지 이미지처럼 보이지만, 길게 누르면 셔터 전후 1.5초씩이 영상으로 재생된다. Live Photo다. 한 장처럼 보이는데 영상이 같이 들어있다는 게 늘 신기했는데, 알고 보니 그 안에 꽤 영리한 설계가 숨어 있었다. 오늘은 Live Photo가 실제로 어떻게 저장되고, 어떻게 하나의 이미지 파일로 보이게 만드는지를 살펴보자.


1. 사실 두 개의 파일이다

Live Photo는 한 파일이 아니라 두 개의 별개 파일이다.

  • 정지 이미지 - HEIC (HEVC로 인코딩된 HEIF). 호환 모드에선 JPEG
  • 영상 - MOV (HEVC H.265, 1080p). 옛날엔 H.264 + 960×720 12~15fps였다

이 둘은 별도로 디스크에 저장된다. 파일명이 같을 필요는 없다고 하는데, 메타데이터로 같은 식별자를 보고 있는지 구분한다.

iCloud나 macOS Photos에서 Live Photo를 직접 추출해보면 정말로 두 파일이 같이 나온다.

text
IMG_1234.HEIC    ← 정지 이미지
IMG_1234.MOV     ← 짝지어진 영상 (3초 분량)

2. 두 파일을 묶는 메커니즘 - Asset Identifier

두 파일을 어떻게 한 짝으로 인식할까? 같은 UUID를 공유한다.

파일메타데이터 위치
HEIC / JPEGkCGImagePropertyMakerAppleDictionary"17" (Apple Maker Note Asset Identifier)
MOVQuickTime metadatacom.apple.quicktime.content.identifier

두 곳에 같은 UUID 문자열을 넣으면 시스템이 한 짝으로 인식한다. 메타데이터의 UUID만 본다.

text
IMG_1234.HEIC: ContentIdentifier = "EFC8E4C5-...-D2B8E1A3F4C5"
IMG_1234.MOV:  ContentIdentifier = "EFC8E4C5-...-D2B8E1A3F4C5"
                                    └─ 같으면 한 짝

이게 의외로 자유로운 설계인데, 다른 사진과 영상도 UUID만 일치시키면 Live Photo로 묶을 수 있다. 실제로 LimitPoint의 LivePhoto 같은 오픈소스 라이브러리가 임의의 사진+영상에 같은 ContentIdentifier를 주입해 Live Photo로 합치는 데 이 점을 활용한다.


3. 영상 안 어느 프레임이 정지 사진인가 - still-image-time

영상이 3초인데, 그중 어느 시점의 프레임이 "셔터를 누른 정지 사진"의 기준일까? 셔터 시점은 보통 영상 한가운데지만, 항상 그런 건 아니다.

이걸 명시하는 게 MOV에 따로 들어있는 timed metadata track이다.

  • 키: com.apple.quicktime.still-image-time
  • 형태: 시간축에 메타데이터를 얹는 별도 트랙

이 트랙이 있어야 시스템이 정확히 이 시점이 정지 사진 임을 알고, Live Photo를 길게 눌렀다 떼는 순간 그 프레임으로 정확히 멈출 수 있다. 없으면 그냥 영상 재생일 뿐이라고..


4. HEIC 컨테이너 자체도 꽤 영리하다

여기서 한 발 더 들어가서 HEIC 자체를 살펴보자. HEIC는 단순한 압축 이미지 포맷이 아니라 컨테이너다.

  • 표준: ISO/IEC 23008-12 (MPEG-H Part 12), 2015
  • 기반: ISOBMFF (ISO Base Media File Format) - MP4·QuickTime의 형제 포맷
  • 파일 구조: 박스(box)들의 트리

각 박스는 4글자 코드(4CC)와 크기 + 페이로드를 가진다.

text
HEIC 파일
├── ftyp  ← FileTypeBox: 호환 brand 명시 (heic, heix, mif1)
├── meta  ← 메타데이터와 image item들
│   ├── iinf  ← item 정보
│   ├── iloc  ← item 위치
│   ├── iref  ← item 간 참조
│   ├── pitm  ← primary item 지정
│   ├── ipco  ← item 속성 모음
│   └── ipma  ← item 속성 매핑
└── mdat  ← 실제 미디어 데이터

핵심은 두 가지 저장 방식이다.

  • 정지 이미지 = item - meta 박스 안에 인덱싱
  • 이미지 시퀀스 = track - 비디오 같은 시간축, 단 핸들러는 'pict' (영상의 'vide' 와 구별됨)

이 덕분에 한 HEIC 파일에 정지 이미지 + 동일 신의 burst + depth map + alpha + edit이 적용된 derived image 가 다 같이 들어갈 수 있다. iPhone의 portrait 모드 사진이 동일 HEIC 파일에 보케 처리 전후 두 버전이 같이 들어가는 게 이 구조 덕분이다.


5. 그런데 왜 Live Photo는 영상을 HEIC 안에 안 넣었을까

HEIF는 영상 트랙도 담을 수 있는 컨테이너다. 그러면 정지 이미지 + 영상을 한 HEIC 안에 다 넣어버리면 깔끔할 텐데, Apple은 일부러 두 파일로 분리했다. 추정 가능한 이유들은 아래와 같은데,

  • iCloud 동기화 효율 - 썸네일이나 단순 보기엔 정지 이미지만 받으면 됨. 영상은 필요할 때만
  • 호환성 - HEIC를 못 읽는 환경에서도 JPEG 폴백이 정지 이미지로 그대로 동작
  • 개별 갱신 - 정지 이미지나 영상 한쪽만 편집되어도 다른 쪽은 그대로
  • 스트리밍 유연성 - 메시지·공유 시트가 정지 이미지를 먼저 보내고 영상은 나중에 보낼 수 있음

요컨대 "묶을 수 있는데 일부러 안 묶고 메타데이터로만 연결"하는 선택이다. 결합도를 낮추는 트레이드오프인 셈이라고 생각한다.


6. Google Motion Photo는 정반대 길을 갔다

같은 문제 - "정지 이미지에 영상 1~2초 곁들이기" - 를 Google은 다르게 풀었다는 얘기가 있다.

Apple Live PhotoGoogle Motion Photo
파일 수2개 (HEIC + MOV)1개 (JPEG/HEIC 안에 MP4 임베드)
연결 방식메타데이터 UUID 페어링XMP Container:Directory로 video offset 명시
영상 위치별도 파일JPEG 끝에 binary 그대로 concatenate
옮기기두 파일을 같이 챙겨야 함단일 파일이라 단순
단점짝 분실 위험XMP 파싱 못 하면 영상 못 찾음

Google은 한 파일에 통째로, Apple은 두 파일에 나눠서. 둘 다 "한 자산처럼 보이게 한다"는 같은 목표를 다른 방식으로 푼 것이라고 한다.

Samsung은 또 비슷하지만 다른 길로 - SEF (Samsung Extra Format) trailer 또는 mpvd/sefd 같은 MP4 top-level box를 쓴다. 그래서 iPhone에서 찍은 Live Photo를 Google Photos로 옮기면 모션이 사라지는 일이 흔하다 - 두 포맷이 호환되지 않기 때문이다.


7. 직접 확인해보기

exiftool 하나면 검증 가능하다.

bash
# 정지 이미지의 ContentIdentifier
exiftool -MakerNotes IMG_1234.HEIC | grep -i identifier
# → Content Identifier: EFC8E4C5-...-D2B8E1A3F4C5
 
# 영상의 ContentIdentifier
exiftool IMG_1234.MOV | grep -i identifier
# → Content Identifier: EFC8E4C5-...-D2B8E1A3F4C5  (동일!)
 
# 영상의 still-image-time 트랙 확인
exiftool -ee IMG_1234.MOV | grep -i still
# → Still Image Time: ...

두 UUID가 일치하지 않으면 Live Photo로 인식되지 않는다. 그래서 USB로 옮긴 뒤 다시 iPhone으로 돌려보내면 종종 모션이 사라지는데, 메타데이터가 중간에 날아간 경우가 대부분이다.


8. PHLivePhoto

두 파일을 묶어 한 자산처럼 다루는 추상은 iOS의 PhotoKit이 제공한다.

  • PHLivePhoto - 정지 이미지 + 영상을 묶은 단일 객체
  • PHAssetResource - 한 자산이 가지는 리소스를 표현. Live Photo의 경우 두 가지 타입 (photo, pairedVideo)을 가짐
  • PHLivePhotoView - UIKit/SwiftUI에서 제스처에 맞춰 정지/재생을 자동 전환
swift
PHLivePhoto.request(
  withResourceFileURLs: [photoURL, videoURL],
  placeholderImage: nil,
  targetSize: .zero,
  contentMode: .aspectFit
) { livePhoto, info in
  // livePhoto를 PHLivePhotoView에 전달하면 끝
}

내부적으로는 두 파일의 ContentIdentifier 매칭 + still-image-time 트랙을 검증한다. UUID가 어긋나거나 still-image-time이 없으면 그냥 정지 사진 + 영상 두 자산으로만 다룬다.


정리

  • Live Photo는 두 개의 파일 - HEIC(또는 JPEG) + MOV - 이 메타데이터 UUID로 묶인 형태
  • 묶음의 핵심: 같은 ContentIdentifier (UUID), 그리고 영상의 com.apple.quicktime.still-image-time 트랙
  • HEIC 자체는 ISOBMFF 기반 컨테이너로 burst/depth/alpha 등 여러 자산을 같이 담을 수 있지만, Apple은 일부러 영상을 별도 파일로 뒀다 - 동기화·호환성·개별 갱신을 위해
  • Google Motion Photo는 정반대 - JPEG 안에 MP4를 통째로 임베드. 같은 문제, 다른 풀이
  • 두 포맷이 호환되지 않아서 iPhone↔Android 간 모션 손실이 흔하다

레퍼런스