안드로이드 → 아이폰 사진/동영상 메타데이터 보정 마이그레이션하기
안드로이드에서 아이폰으로 사진·동영상을 옮길 때 촬영일이 꼬이지 않도록, 파일의 수정시간을 기반으로 EXIF 메타데이터를 자동 보정해주는 실전 마이그레이션 자동화 경험과 노하우를 정리한 글입니다.
ChangBeen Seo
최근에 안드로이드 폰을 사용하던 중, 아이폰으로 갈아타게 되었습니다. 사진과 동영상 수천 장을 iPhone으로 옮기는 과정에서 생각보다 불편한 이슈가 있었는데요, 바로:
✅ 사진/동영상의 "촬영일"이 제대로 유지되지 않는 문제
안드로이드에서 백업한 파일들을 iPhone의 사진 앱으로 옮기면, 촬영 날짜가 아닌 복사된 날짜를 기준으로 정렬되어버립니다.
예쁜 추억들이 뒤죽박죽 섞여버리는 기분… 너무 불편했죠. 그 원인은 간단했습니다:
- ❌ EXIF 메타데이터가 없는 파일들
- WhatsApp, 카카오톡 공유 이미지
- 스크린샷
- 오래된 영상들
🛠️ 그래서 직접 자동화 스크립트를 만들었습니다
🎯 목표
- 원본 파일을 손상시키지 않고
- 메타데이터가 없는 파일에 한해
- Android의 수정시간(mtime)을 기반으로
- 정확한 EXIF 촬영일(DateTimeOriginal, CreateDate 등)을 설정
그 결과, iPhone으로 복사해도 날짜 정렬이 완벽하게 복원됩니다.
🏃♂️ 전체 마이그레이션 실행 예시
- ADB 무선 연결
adb pair 192.168.0.X:port adb connect 192.168.0.X:5555 - 스크립트 실행
node migrate-photo-metadata.js - iPhone에 복사
- Finder/파일 탐색기에서 iPhone 연결 후, 보정된 사진/영상을 복사
⚙️ 스크립트 구성 요약
- 📂 대상 디렉터리:
/sdcard/Pictures/KakaoTalk - 📥 작업 디렉터리:
./kakao
사용한 주요 기술 스택
- Node.js
- exiftool-vendored: EXIF 조작
- p-limit: 병렬 처리 제어
- adb shell, adb pull: Android 기기에서 파일 조회 및 복사
📦 처리 방식 요약
- Android 원격 디렉터리에서 전체 파일 목록 가져오기
- 각 파일의 수정시간(mtime)을 stat -c %y로 확인
- 로컬로 adb pull
- ExifTool로 메타데이터 존재 여부 확인
- 없다면 Android mtime을 기반으로 촬영일을 설정
- 병렬로 처리하되 안정성을 위해 최대 5~10개로 제한
- .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, 병렬 처리, 포맷별 대응을 통해 안정성과 성능도 확보 가능
안드로이드에서 아이폰으로 갈아타시는 분들 중, 사진이 날짜 순서로 안 보인다는 경험을 하신 분이 있다면 이 스크립트가 조금이나마 도움이 되길 바랍니다.