Beenslab Blog

안드로이드 → 아이폰 사진/동영상 메타데이터 보정 마이그레이션하기

beenchangseo·2025년 7월 3일Hits

📷 안드로이드 → 아이폰 사진/동영상 메타데이터 보정 마이그레이션하기

최근에 안드로이드 폰을 사용하던 중, 아이폰으로 갈아타게 되었습니다. 사진과 동영상 수천 장을 iPhone으로 옮기는 과정에서 생각보다 불편한 이슈가 있었는데요, 바로:

사진/동영상의 "촬영일"이 제대로 유지되지 않는 문제

안드로이드에서 백업한 파일들을 iPhone의 사진 앱으로 옮기면, 촬영 날짜가 아닌 복사된 날짜를 기준으로 정렬되어버립니다.

예쁜 추억들이 뒤죽박죽 섞여버리는 기분… 너무 불편했죠. 그 원인은 간단했습니다:

  • EXIF 메타데이터가 없는 파일들
    • WhatsApp, 카카오톡 공유 이미지
    • 스크린샷
    • 오래된 영상들

🛠️ 그래서 직접 자동화 스크립트를 만들었습니다

🎯 목표

  • 원본 파일을 손상시키지 않고
  • 메타데이터가 없는 파일에 한해
  • Android의 수정시간(mtime)을 기반으로
  • 정확한 EXIF 촬영일(DateTimeOriginal, CreateDate 등)을 설정

그 결과, iPhone으로 복사해도 날짜 정렬이 완벽하게 복원됩니다.


🏃‍♂️ 전체 마이그레이션 실행 예시

  1. ADB 무선 연결
    adb pair 192.168.0.X:port
    adb connect 192.168.0.X:5555
    
  2. 스크립트 실행
    node migrate-photo-metadata.js
    
  3. iPhone에 복사
    • Finder/파일 탐색기에서 iPhone 연결 후, 보정된 사진/영상을 복사

⚙️ 스크립트 구성 요약

  • 📂 대상 디렉터리: /sdcard/Pictures/KakaoTalk
  • 📥 작업 디렉터리: ./kakao

사용한 주요 기술 스택

  • Node.js
  • exiftool-vendored: EXIF 조작
  • p-limit: 병렬 처리 제어
  • adb shell, adb pull: Android 기기에서 파일 조회 및 복사

📦 처리 방식 요약

  1. Android 원격 디렉터리에서 전체 파일 목록 가져오기
  2. 각 파일의 수정시간(mtime)을 stat -c %y로 확인
  3. 로컬로 adb pull
  4. ExifTool로 메타데이터 존재 여부 확인
  5. 없다면 Android mtime을 기반으로 촬영일을 설정
  6. 병렬로 처리하되 안정성을 위해 최대 5~10개로 제한
  7. .gif 파일은 에러 발생 가능성 높아 스킵

🔍 마주친 문제와 해결 과정

1) ExifTool "Not a valid JPG" 에러 → GIF 제외

Not a valid JPG (looks more like a GIF)
  • .gif 확장자를 만나면 바로 continue 하도록 코드에서 예외 처리

2) pull 시점에 로컬 mtime으로 덮어쓰기

  • Android 기기에서 stat -c %y원격 수정시간을 미리 가져와 EXIF 설정값으로 사용
  • 복사 시점의 로컬 mtime이 아닌, 실제 촬영/수정 시점을 보존

3) 공백·특수문자 포함 파일 경로 인식 실패

stat: '/sdcard/.../Screenshot_20201103-020129_Among Us.jpg': No such file
  • 로컬 셸(adb shell "…")에서 원격 셸 파일경로를 작은따옴표로 감싸도록 수정
// 원격 stat (공백 파일명 지원)
async function getRemoteMtime(remotePath) {
  // 로컬 셸에서 전체를 큰따옴표로 감싸고,
  // 원격 셸에서 파일 경로를 작은따옴표로 감싼다
  const cmd = `adb shell "stat -c %y '${remotePath}'"`;
  const { stdout } = await exec(cmd);
  const d = new Date(stdout.trim());
  if (isNaN(d)) throw new Error(`Invalid date from "${stdout.trim()}"`);
  return d;
}

4) 느린 순차 처리 → p-limit 동적 import & 병렬화

  • 파일이 수천 개일 때 순차적으로 처리하면 시간이 너무 오래 걸림
  • p-limit을 동적으로 import하여 동시 5~10개로 병렬 처리
  • 안정성과 성능을 모두 확보

🔥 코드 발췌

// 원격 stat (공백 파일명 지원)
async function getRemoteMtime(remotePath) {
  const cmd = `adb shell "stat -c %y '${remotePath}'"`;
  const { stdout } = await exec(cmd);
  return new Date(stdout.trim());
}

// 메타데이터가 없다면 수정시간 기반으로 설정
const tags = VIDEO_EXTS.includes(ext)
  ? { CreateDate: exifDate, ModifyDate: exifDate }
  : { AllDates: exifDate, CreateDate: exifDate, ModifyDate: exifDate };

await exiftool.write(localPath, tags, ['-overwrite_original']);

📈 성능 및 결과

  • 처리 파일 수: 약 5,000개
  • 총 용량: 10GB
  • 병렬 처리: p-limit으로 동시 5~10개 제한
  • 처리 시간: 약 8~10분
  • 결과: 아이폰 사진 앱에서 정확하게 '촬영일 기준'으로 정렬됨

⚠️ 처리 예외

  • .gif 파일은 Not a valid JPG 오류 발생 가능성이 높아 스킵 처리
  • .webp, .heic 등의 포맷은 필요 시 확장자 목록을 추가하면 됨

🌐 GitHub

안드로이드에서 아이폰으로 미디어를 마이그레이션 하고싶다면, android-iphone-media-migrator 깃허브에서 소스코드를 참고해보세요!

✨ 마무리하며

이번 작업을 통해 얻은 인사이트:

  • EXIF 메타데이터의 중요성
  • 촬영일 정보가 없는 사진/영상은 아이폰에서 매우 불편하게 정렬됨
  • 간단한 ADB + ExifTool 스크립트로 이 문제를 100% 해결할 수 있음
  • 무선 ADB, 병렬 처리, 포맷별 대응을 통해 안정성과 성능도 확보 가능

안드로이드에서 아이폰으로 갈아타시는 분들 중, 사진이 날짜 순서로 안 보인다는 경험을 하신 분이 있다면 이 스크립트가 조금이나마 도움이 되길 바랍니다.