설치 · 사용법

ExifTool로 JPEG 촬영 시각 보정 — 사본 시간 이동·원본 보존

먼저 확인

카메라 시계가 한 시간 느렸다는 사실을 알고 있다면 JPEG 한 장의 사본에서 EXIF DateTimeOriginal을 한 시간 더한 새 파일을 만들 수 있습니다. 원본은 따로 보관하고 보정한 날짜를 다시 읽어 확인하세요. 이 절차는 실제 촬영 시각이나 시간대를 추측하지 않으며 XMP·CreateDate·파일 수정 시각을 함께 고치지 않습니다.

시각 보정 조건

대상
EXIF DateTimeOriginal이 있는 .jpg 한 장
보정 기준
촬영 때의 실제 시계 오차와 방향을 알고 있을 때
결과
촬영 시각을 이동한 새 JPEG 사본
원본
별도 폴더 보관 · 원본과 입력 사본 SHA256 대조
실행 환경
Windows PowerShell 7.3+ · Windows/Standard 인수 전달
범위
EXIF 촬영 시각만 · 일괄 처리·시간대 추론 제외

1. 시각 보정·확인 순서

  1. 1. 오차 확인

    신뢰할 수 있는 촬영 기록과 카메라 시계를 비교해 더할지 뺄지 정합니다. 기준을 모르면 보정하지 않습니다.

  2. 2. 원본·입력·출력 분리

    원본은 별도 폴더에 두고 입력 사본과 새 출력 경로를 준비합니다. 기존 파일이 있는 출력 경로는 사용하지 않습니다.

  3. 3. 현재 EXIF 값 읽기

    ExifIFD의 DateTimeOriginal과 다른 날짜·시간대·초 미만 값을 확인합니다. EXIF 촬영 시각이 없으면 멈춥니다.

  4. 4. 알려진 시간 차이 적용

    UTF-8 인수 파일로 JPG 한 장의 EXIF DateTimeOriginal만 이동한 새 사본을 만듭니다.

  5. 5. 시각·원본·이미지 대조

    출력 촬영 시각과 날짜 경계를 다시 읽고 원본 SHA256, 다른 태그와 표시 방향·색상을 확인합니다.

2. 보정할 시간 차이부터 확인

사진에 기록된 시간이 실제 촬영보다 한 시간 느렸다면 +1시간, 한 시간 빨랐다면 −1시간이 필요합니다. 함께 촬영한 기준 사진이나 신뢰할 수 있는 기록과 비교하세요. 여행 중 시간대 변경, 서머타임, 카메라 시계 오류는 원인이 다르므로 폴더 이름이나 파일 수정 시각만 보고 보정량을 정하지 마세요.

이 절차는 EXIF:DateTimeOriginal의 시각 문자열만 이동합니다. OffsetTimeOriginal의 시간대 오프셋, SubSecTimeOriginal의 초 미만 값, XMP 날짜와 파일 시스템 날짜는 별도 정보입니다. 오프셋을 새로 설정하거나 UTC와 현지 시각을 자동 변환하는 과정은 포함하지 않습니다.

3. JPG 한 장과 새 출력 경로 준비

ExifTool 배포·실행 파일 준비를 확인한 뒤 Windows의 PowerShell 7.3 이상에서 진행합니다. Windows PowerShell 5.1, 명령 프롬프트, Legacy 인수 전달 모드는 이 예제의 대상이 아닙니다. Windows ZIP의 exiftool_files 폴더는 실행 파일과 함께 유지하세요.

탐색기에서 C:\Photo-check\원본과 C:\Photo-check\작업 두 폴더를 만들고, 원본 폴더에 보정할 photo.jpg 한 장을 둡니다. 아래 실행 파일 경로와 사진 경로를 자신의 경로로 바꾸세요. 입력 photo.jpg와 출력 photo-fixed.jpg는 작업 폴더에 아직 없어야 합니다. 각 코드 묶음을 같은 PowerShell 창에서 순서대로 실행합니다.

if ($PSVersionTable.PSVersion -lt [version]'7.3') { throw 'PowerShell 7.3 이상에서 실행하세요.' }
if ($PSNativeCommandArgumentPassing -notin @('Windows', 'Standard')) { throw '기본 인수 전달 모드를 확인하세요.' }
$shiftExe = 'C:\Tools\ExifTool\exiftool.exe'
$shiftOriginal = 'C:\Photo-check\원본\photo.jpg'
$shiftInput = 'C:\Photo-check\작업\photo.jpg'
$shiftOutput = 'C:\Photo-check\작업\photo-fixed.jpg'
Set-Location -LiteralPath 'C:\Photo-check\작업'
& $shiftExe -config '' -ver
if ($LASTEXITCODE -ne 0) { throw 'ExifTool 버전 확인에 실패했습니다.' }
if ([IO.Path]::GetExtension($shiftOriginal) -ine '.jpg') { throw 'JPG 한 장만 선택하세요.' }
if ((Test-Path -LiteralPath $shiftInput) -or (Test-Path -LiteralPath $shiftOutput)) { throw '입력·출력에 새 파일 경로를 사용하세요.' }
$shiftOriginalHash = (Get-FileHash -LiteralPath $shiftOriginal -Algorithm SHA256).Hash
Copy-Item -LiteralPath $shiftOriginal -Destination $shiftInput -ErrorAction Stop

새 작업에서 한 번만 실행

이 코드는 작업 폴더로 이동한 뒤 원본을 입력 사본으로 복사합니다. 경로·버전 확인이 실패하거나 기존 입력·출력이 있으면 멈추세요. 새 시험에는 다른 작업 폴더와 새 파일 이름을 사용합니다. 기존 사진을 지우거나 덮어써서 예제를 재실행하지 마세요.

4. 현재 EXIF 촬영 시각과 다른 날짜 읽기

@('-G1', '-a', '-s', '-EXIF:DateTimeOriginal', '-EXIF:CreateDate', '-EXIF:ModifyDate', '-EXIF:OffsetTimeOriginal', '-EXIF:SubSecTimeOriginal', '-XMP:DateTimeOriginal', '-Orientation', '-ColorSpace', $shiftInput) | Out-File -LiteralPath '.\time-read-before.txt' -Encoding utf8NoBOM -NoClobber -ErrorAction Stop
& $shiftExe -config '' -charset filename=UTF8 -@ '.\time-read-before.txt'
if ($LASTEXITCODE -ne 0) { throw '현재 태그 읽기에 실패했습니다.' }

출력의 [ExifIFD] DateTimeOriginal이 실제 보정 대상입니다. 이 줄이 없거나 XMP에만 날짜가 있으면 다음 단계로 넘어가지 않습니다. CreateDate, ModifyDate, OffsetTimeOriginal, SubSecTimeOriginal, XMP 날짜, Orientation, ColorSpace도 함께 기록해 출력과 비교하세요. 존재하지 않는 태그는 출력에 나타나지 않습니다.

Out-File은 UTF-8 BOM 없는 인수 파일을 만들고 -NoClobber는 같은 이름의 인수 파일 덮어쓰기를 막습니다. -charset filename=UTF8을 -@보다 앞에 두어 한글·공백 경로를 전달합니다. -config ''는 사용자 설정 파일을 읽지 않도록 빈 설정 경로를 전달합니다.

5. 한 시간 이동한 새 JPEG 사본 만들기

다음 예제는 카메라가 한 시간 느렸을 때 한 시간을 더합니다. 실제로 필요한 보정량이 한 시간이 아니라면 그대로 실행하지 마세요. 인수 배열의 각 문자열이 파일의 한 줄이 되며, '$EXIF:DateTimeOriginal'의 작은따옴표는 PowerShell 문자열을 보호하는 용도입니다. 인수 파일에 별도 따옴표를 추가하지 않습니다.

@('-if', '$EXIF:DateTimeOriginal', '-o', $shiftOutput, '-EXIF:DateTimeOriginal+=0:0:0 1:0:0', $shiftInput) | Out-File -LiteralPath '.\time-shift-args.txt' -Encoding utf8NoBOM -NoClobber -ErrorAction Stop
& $shiftExe -config '' -charset filename=UTF8 -@ '.\time-shift-args.txt'
$shiftExitCode = $LASTEXITCODE
$shiftExitCode

-if는 EXIF 촬영 시각이 있는 입력만 처리합니다. -o는 새 출력 경로를 지정하고 -EXIF:DateTimeOriginal+=0:0:0 1:0:0은 년·월·일 0, 시 1, 분·초 0을 더합니다. 한 시간 빼는 경우는 -EXIF:DateTimeOriginal-=0:0:0 1:0:0으로 바꾸되, 원본에서 시작한 새 입력 사본과 새 출력·인수 파일 이름을 사용하세요.

원본 보존 옵션을 유지

-overwrite_original을 추가하지 마세요. 이 옵션을 -o와 함께 쓰면 새 출력이 만들어진 뒤 입력 원본을 삭제하는 동작이 됩니다. 이 예제는 입력 사본도 그대로 남기는 방식입니다.

ExifTool 바로 다음 줄에서 종료 코드를 저장합니다. 0은 명령의 성공 상태이고, 조건을 통과한 입력이 없으면 2, 오류가 있으면 1이 반환될 수 있습니다. 코드가 0이어도 촬영 시각이 실제로 맞다는 증명은 아니므로 반드시 다음 대조를 수행합니다.

6. 보정 날짜·다른 태그·원본 대조

if ($shiftExitCode -ne 0 -or -not (Test-Path -LiteralPath $shiftOutput)) { throw '새 사본이 만들어졌는지 확인하고 여기서 멈추세요.' }
@('-G1', '-a', '-s', '-EXIF:DateTimeOriginal', '-EXIF:CreateDate', '-EXIF:ModifyDate', '-EXIF:OffsetTimeOriginal', '-EXIF:SubSecTimeOriginal', '-XMP:DateTimeOriginal', '-Orientation', '-ColorSpace', $shiftOutput) | Out-File -LiteralPath '.\time-read-after.txt' -Encoding utf8NoBOM -NoClobber -ErrorAction Stop
& $shiftExe -config '' -charset filename=UTF8 -@ '.\time-read-after.txt'
if ($LASTEXITCODE -ne 0) { throw '출력 태그 읽기에 실패했습니다.' }
if ((Get-FileHash -LiteralPath $shiftOriginal -Algorithm SHA256).Hash -ne $shiftOriginalHash) { throw '원본이 달라졌습니다.' }
if ((Get-FileHash -LiteralPath $shiftInput -Algorithm SHA256).Hash -ne $shiftOriginalHash) { throw '입력 사본이 달라졌습니다.' }
'원본과 입력 사본의 SHA256이 유지되었습니다.'

보정 전후 [ExifIFD] DateTimeOriginal이 의도한 만큼 달라졌는지 비교합니다. 예를 들어 2024:02:29 23:30:00에 한 시간을 더하면 2024:03:01 00:30:00입니다. CreateDate·ModifyDate·XMP 날짜와 OffsetTimeOriginal·SubSecTimeOriginal은 이 명령이 함께 보정하지 않습니다. 다른 날짜를 임의로 일치시키지 마세요.

SHA256 검사는 원본과 입력 사본의 바이트가 유지됐는지 확인합니다. 출력 사본은 메타데이터가 바뀌므로 입력과 같은 해시를 요구하지 않습니다. 태그 읽기만으로 화면 표시까지 검증되지는 않으므로 사진을 열어 크기·회전·색상도 비교하세요. ICC 프로필의 존재와 상세 정보는 필요하면 별도로 읽어 확인합니다. 출력의 파일 수정 시각은 촬영 시각과 별개입니다.

직접 만든 64×48 JPEG에서 확인한 시간 이동
입력 EXIF 촬영 시각 이동 출력 EXIF 촬영 시각
2024:02:29 23:30:00 +1시간2024:03:01 00:30:00
2024:02:29 23:30:00 −1시간2024:02:29 22:30:00
2023:12:31 23:30:00 +1시간2024:01:01 00:30:00

자체 시험은 ExifTool 13.59 x64와 PowerShell 7.6.5의 Windows 인수 전달 모드에서 합성 JPEG만 사용했습니다. 한국어·공백 경로, EXIF 촬영 시각이 없는 입력과 XMP에만 날짜가 있는 입력의 건너뛰기, 기존 출력 파일 거부도 확인했습니다. 원본 해시·다른 EXIF/XMP 태그·ICC·Orientation·디코딩한 픽셀·크기가 유지됐습니다. 실제 카메라 사진이나 MakerNotes, RAW·HEIC·동영상, 모든 사진 앱의 표시·정렬은 시험하지 않았습니다.

7. 멈출 조건과 다음 작업

EXIF 촬영 시각이 없거나 값이 올바른 날짜가 아닌 경우, 보정 기준이 불명확한 경우에는 멈춥니다. 기존 출력 경로를 사용해 오류가 났다면 그 파일을 보존하고 새 작업 경로를 정하세요. 원본 해시나 의도하지 않은 태그·표시 색상·방향이 달라지면 출력 사본을 채택하지 말고 보관한 원본을 기준으로 확인합니다.

되돌릴 때는 따로 보관한 원본에서 다시 시작하는 편이 분명합니다. 보정한 사본에 반대 오차를 반복 적용하거나 다른 날짜 필드를 덮어쓰지 마세요. 촬영 시각을 확정한 뒤 파일명을 정리하려면 JPEG 촬영일 파일명 미리보기·중복 이름 확인으로 이어집니다. 이름 변경은 이 가이드의 태그 수정과 별도 작업입니다.

8. 자주 묻는 질문

모든 사진 날짜가 함께 보정되나요?

+

아니요. EXIF DateTimeOriginal만 이동합니다. CreateDate·ModifyDate·XMP 날짜·파일 수정 시각은 별개이며 함께 고치지 않습니다.

촬영일이 없는 사진에도 쓸 수 있나요?

+

이 예제는 EXIF 촬영 시각이 있는 JPG 한 장만 처리합니다. 날짜가 없거나 XMP에만 있는 입력은 건너뜁니다. 없는 날짜를 추측해 새로 만들지 않습니다.

여행지 시간대로 자동 변환하나요?

+

자동 변환하지 않습니다. 알고 있는 시계 오차만 적용하며 OffsetTimeOriginal을 바꾸거나 실제 시간대를 추론하지 않습니다.

사진 이름이나 원본도 바뀌나요?

+

새 출력 사본을 만들고 원본과 입력 사본의 해시를 대조합니다. 예제의 새 출력 이름은 사용자가 지정하며 기존 사진의 이름을 바꾸는 명령은 아닙니다.

보정 결과를 되돌리려면 어떻게 하나요?

+

별도 보관한 원본으로 돌아가 보정량을 다시 확인하세요. 이미 보정한 파일에 반복 적용하지 말고 새 입력 사본과 새 출력 경로에서 시작합니다.

사진 앱에는 왜 다른 날짜가 보이나요?

+

앱에 따라 EXIF·XMP·파일 시스템 날짜 중 사용하는 값과 시간대 해석이 다를 수 있습니다. ExifTool에서 보정한 그룹·태그의 값부터 확인하고 앱의 표시 기준을 별도로 확인하세요.

9. 참고 자료

관련 가이드

함께 보면 좋은