안드로이드 → 아이폰 사진/동영상 메타데이터 보정 마이그레이션하기
📷 안드로이드 → 아이폰 사진/동영상 메타데이터 보정 마이그레이션하기
최근에 안드로이드 폰을 사용하던 중, 아이폰으로 갈아타게 되었습니다. 사진과 동영상 수천 장을 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, 병렬 처리, 포맷별 대응을 통해 안정성과 성능도 확보 가능
안드로이드에서 아이폰으로 갈아타시는 분들 중, 사진이 날짜 순서로 안 보인다는 경험을 하신 분이 있다면 이 스크립트가 조금이나마 도움이 되길 바랍니다.