同一張照片重存後,還讀得到位置嗎?metadata-extractor 真實照片實測
重點摘要
- 用獨立來源的真實照片,下載原檔讀出相機型號、拍攝時間與GPS三項資料。
- 同一照片經Java ImageIO重存後,這三項均讀不到;兩檔解析錯誤均為0。
- 原檔與重存檔均為3765×5647;相同尺寸不代表附帶資料相同。
- metadata-extractor負責讀取;本次重存由Java ImageIO執行。
- 結論: 傳原檔前可先讀取附帶資料;不能只看畫面判斷。
- 適合: 會寫Java、想批次檢查照片資料的人
- 不適合: 需要現成圖形介面或一鍵清除位置的人
| 實測項目 | 結果 |
|---|---|
| 輸入 | 台北101真實照片下載原檔及其ImageIO重存檔 |
| 原檔 | 三項皆讀到 |
| 重存 | 三項皆讀不到 |
| 解析錯誤 | 兩檔均0 |
旅行回來要把照片原檔傳給朋友,除了看畫面,你可能還想知道檔案附帶什麼資料。這一集實測 metadata-extractor,用的是獨立於專案的真實台北101照片,不再用它附帶的測試檔。測試不需要把私人照片或原始座標放進影片;影片呈現必要結果,完整輸入與輸出留作本機證據。這張公開照片也不是我們親自拍的。
測什麼、為什麼這樣測
這次直接讀取 Balon Greyjoy 拍攝、以 CC0 公開的照片下載原檔。接著以 Java ImageIO 重新輸出同一張照片,再交給同一個讀取工具檢查。問題是:畫面看起來相同,附帶的相機、時間、座標是否仍相同?這個對照符合傳檔前檢查的用途,不是把人造座標寫進照片,或拿預設答案做一次展示。
設備為 Mac mini M4 Pro(12核、64GB),執行環境是 Debian 12、OpenJDK 17.0.21;受測程式庫版本固定為 2.19.0,依賴 xmpcore 6.1.11。沒有呼叫付費 API。
準備與下載
需要 Java 17 JDK,java -version 與 javac -version 都要成功。它是 Java 程式庫,沒有點兩下就能操作的視窗。請在獨立資料夾下載兩個 jar,再放入想檢查的照片。本文使用的公開原檔可照下面取得;讀自己的照片時,只需換成本機檔名,不必上傳。
mkdir photo-check
cd photo-check
curl -fLO https://repo1.maven.org/maven2/com/drewnoakes/metadata-extractor/2.19.0/metadata-extractor-2.19.0.jar
curl -fLO https://repo1.maven.org/maven2/com/adobe/xmp/xmpcore/6.1.11/xmpcore-6.1.11.jar
curl -fL -o original.jpg https://upload.wikimedia.org/wikipedia/commons/4/4b/20190416_Taipei_101-1.jpg
CP=metadata-extractor-2.19.0.jar:xmpcore-6.1.11.jar
先查原檔,再重存檢查
把下列內容存成 Inspect.java。讀取每張檔案後,它分別列出相機型號、原始拍攝時間、座標與解析錯誤數。讀不到欄位和程式執行失敗是兩回事;若讀檔例外,程式會停止,不能把失敗當作沒有座標。
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.*;
import com.drew.metadata.exif.*;
import com.drew.lang.GeoLocation;
import java.io.File;
public class Inspect {
public static void main(String[] args) throws Exception {
for (String path : args) {
Metadata m=ImageMetadataReader.readMetadata(new File(path));
ExifIFD0Directory e=m.getFirstDirectoryOfType(ExifIFD0Directory.class);
ExifSubIFDDirectory d=m.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
GpsDirectory g=m.getFirstDirectoryOfType(GpsDirectory.class);
GeoLocation l=g==null?null:g.getGeoLocation();
System.out.println(new File(path).getName());
System.out.println("Model: "+(e==null?"not found":e.getString(ExifIFD0Directory.TAG_MODEL)));
System.out.println("Date: "+(d==null?"not found":d.getString(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)));
System.out.println("GPS: "+(l==null?"not found":l.getLatitude()+", "+l.getLongitude()));
int errors=0; for(Directory dir:m.getDirectories()) for(String err:dir.getErrors()){errors++;System.err.println(err);}
System.out.println("Errors: "+errors);
}
}
}
再將以下存成 Reencode.java。這是用來重新輸出 JPEG 的另一段程式,不是 metadata-extractor 的清除功能。JPEG 重存是有損壓縮;雖然尺寸不變,像素不保證完全相等。這次是檢查欄位差異,不是推薦把重存當成通用隱私保證。
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
public class Reencode {
public static void main(String[] a) throws Exception {
BufferedImage img=ImageIO.read(new File(a[0]));
if(img==null || !ImageIO.write(img,"jpg",new File(a[1]))) throw new Exception("JPEG conversion failed");
BufferedImage b=ImageIO.read(new File(a[1]));
System.out.println("Reencoded pixels: "+img.getWidth()+"x"+img.getHeight()+" -> "+b.getWidth()+"x"+b.getHeight());
}
}
javac -cp "$CP" Inspect.java Reencode.java
java Reencode original.jpg reencoded.jpg
java -cp "$CP:." Inspect original.jpg reencoded.jpg
實際結果
| 檢查欄位 | 下載原檔 | ImageIO 重存檔 |
|---|---|---|
| 相機型號 | Canon EOS 5D Mark III | 讀不到 |
| 拍攝時間 | 2019:04:16 14:33:44 | 讀不到 |
| GPS | 讀得到 | 讀不到 |
| 解析錯誤 | 0 | 0 |
| 影像尺寸 | 3765×5647 | 3765×5647 |

這裡的三項,專指表格前三項,不是說原檔只有三個資料欄位。工具從原檔讀出的座標與 Commons 頁面的公開拍攝位置相符,但欄位本身可以修改,不能據此證明一個人當時真的在哪裡。重存檔這三項都讀不到,表示這次重新輸出沒有保留它們,不表示所有轉檔軟體都如此。
適合誰、遇到問題怎麼辦
我的看法:會寫程式、要批次檢查照片附帶資料的人,用它能把檢查整合進工作流程。完全不寫程式的人,這個程式庫仍有上手門檻。出現 javac: command not found,要確認裝的是 JDK;出現缺少 com/adobe/xmp,要確認兩個 jar 都列在 classpath。上述指令適用 Linux/macOS,Windows 的 classpath 分隔符號須改為分號。
這次沒有測通訊軟體上傳、手機截圖或 HEIC,不推論它們是否保留位置;也沒有測復原被移除的資料。結論是:不能只看照片畫面,就判斷原檔帶了什麼;先用工具讀一次,才能知道這份檔案實際可讀到哪些欄位。
專案:metadata-extractor,Apache-2.0。 照片:20190416 Taipei 101-1.jpg,Balon Greyjoy,CC0 1.0。影片只展示測試結果,原圖不入鏡;資料來源留存於本機證據。