iPhone 把截圖存成 HEIC 卻命名成 .jpg

iPhone 把截圖存成 HEIC 卻命名成 .jpg

API 回傳 400 錯誤,訊息說「格式不支援」。同一張圖手動重送又過了。這種間歇性錯誤最煩,因為你不知道問題在哪個環節。

逛夜市的時候,你在一個攤位前停下來,攤主打著「萬巒豬腳」的紅色布條,切下去卻發現是重組肉。文件說是豬腳,切開才知道不是那個東西。

副檔名是 .jpg,內容是 HEIC

錯誤發生在一個 AI 影片腳本生成的 workflow 裡。使用者上傳截圖,伺服器讀取檔案,送進 Claude API 分析畫面內容。流程很直觀,但 iPhone 截圖就是過不了。

檔案名稱是 IMG_1234.jpg,伺服器根據副檔名判斷 MIME type 是 image/jpeg,打包送給 API。API 收到後看了一眼檔案開頭的 bytes,發現不對勁,直接拒絕。因為那不是 JPEG,是 HEIC。

iPhone 從 iOS 11 開始預設把照片存成 HEIC 格式,壓縮率比 JPEG 好,檔案小一半。但為了相容性,分享或傳輸時會自動轉成 JPEG。問題是截圖這個動作沒有經過「分享」流程,系統直接把 HEIC 檔案存下來,副檔名卻還是 .jpg。

Magic bytes 才是真相

每種檔案格式的開頭都有固定的 bytes 簽名,叫做 magic number。JPEG 的開頭是 FF D8 FF。PNG 是 89 50 4E 47。HEIC 的簽名藏在檔案的第 4 到 11 byte 之間,內容是 ftyp 加上 heic 或 mif1。

解法不是改副檔名,也不是事後轉檔。那些都是補洞,而且會拖慢整個流程。正確做法是讀檔案的前 12 個 bytes,根據 magic number 判斷真實格式。不支援的格式直接跳過,該回傳錯誤就回傳,不要送進 API 浪費 quota。

程式碼很短。打開檔案,讀前面幾個 bytes,比對已知的簽名表。JPEG、PNG、WebP、HEIC、TIFF,每種格式都有自己的指紋。比對成功就標記格式,比對失敗就拒絕處理。

副檔名從來不可靠

這件事違反直覺的地方在於:副檔名是我們最信任的格式判斷依據,但 iPhone 這個設計讓副檔名完全失去了意義。你以為拿到的是 JPEG,打開才發現是別的東西。

問題不只出現在 iPhone 截圖。Windows 系統可以手動改副檔名,Linux 根本不在乎副檔名,只看 MIME type。副檔名只是給人類看的標籤,不是給程式看的協議。任何依賴副檔名判斷格式的邏輯,遲早會踩坑。

修完這個 bug 之後,workflow 的錯誤率從 15% 降到 0.3%。那 0.3% 是真的格式損壞或網路傳輸問題,不是因為檔案偽裝。

— 邱柏宇

延伸閱讀


When iPhone Screenshots Lie About Their Format

The API returned a 400 error with the message “format not supported.” The same image sent manually worked fine. Intermittent errors like this are the worst because you don’t know which part of the pipeline is broken.

It’s like stopping at a night market stall with a banner advertising “Wanluan pork knuckle,” only to bite into restructured meat. The label says one thing, but the actual product is something else entirely.

The Extension Says .jpg, the Content is HEIC

The error occurred in an AI video script generation workflow. Users upload screenshots, the server reads the file, and sends it to Claude API for content analysis. The process is straightforward, but iPhone screenshots consistently failed.

The filename was IMG_1234.jpg. The server determined the MIME type based on the extension—image/jpeg—and packaged it for the API. Upon receiving the file, the API examined the first few bytes, detected a mismatch, and rejected it outright. Because it wasn’t a JPEG. It was HEIC.

Since iOS 11, iPhones save photos in HEIC format by default—better compression, half the file size. For compatibility, photos are auto-converted to JPEG when shared. But screenshots bypass the “share” workflow entirely. The system saves the HEIC file directly but keeps the .jpg extension.

Magic Bytes Tell the Truth

Every file format has a fixed byte signature at its beginning, called a magic number. JPEG starts with FF D8 FF. PNG with 89 50 4E 47. HEIC’s signature hides between bytes 4 and 11, containing ftyp followed by heic or mif1.

The solution isn’t renaming extensions or post-conversion. Those are patches that slow down the entire process. The correct approach is reading the first 12 bytes of the file and determining the real format based on the magic number. Unsupported formats get skipped immediately. No wasted API quota.

The code is short. Open the file, read the first few bytes, compare against a known signature table. JPEG, PNG, WebP, HEIC, TIFF—each format has its fingerprint. Match found, tag the format. No match, reject processing.

Extensions Were Never Reliable

What’s counterintuitive here is that file extensions are our most trusted method for format identification, but iPhone’s design completely strips that trust away. You think you received a JPEG, then discover it’s something else entirely.

The problem isn’t limited to iPhone screenshots. Windows allows manual extension changes. Linux ignores extensions altogether, relying solely on MIME types. Extensions are labels for humans, not contracts for programs. Any logic depending on extensions for format detection will eventually break.

After fixing this bug, the workflow’s error rate dropped from 15% to 0.3%. That remaining 0.3% represents actual file corruption or network issues, not files in disguise.

— 邱柏宇

Related Posts