MarkItDown 的核心不是「讀懂檔案」,而是「猜測檔案是哪種格式,然後交給對應的 converter」。
所有格式都遵循同一個 DocumentConverter 協定(accepts() + convert()),
由 MarkItDown._convert() 依優先序逐一嘗試,直到有人成功。
StreamInfo 是描述「這個檔案看起來是什麼」的資料結構,欄位含
mimetype / extension / charset / filename / local_path / url。
來源可以是副檔名、Content-Type header、Content-Disposition 的 filename、或 URL 的副檔名。
StreamInfo(
mimetype="application/pdf",
extension=".pdf",
filename="report.pdf",
local_path="report.pdf",
)
_get_stream_info_guesses() 會再呼叫 magika(Google 的內容型別偵測模型,
純本機、零網路)直接讀檔案位元組猜型別,並用 charset-normalizer 猜文字編碼。
如果 magika 的猜測與線索衝突(例如副檔名說 .txt 但內容是 HTML),
就會產生多個 guess,依序嘗試。
每個 converter 註冊時帶一個 priority:越小越先試。
內建 converter 大多為 0.0(特定格式);PlainTextConverter / HtmlConverter / ZipConverter
是 10.0(通用兜底,放最後)。排序是穩定排序,同優先級保持註冊順序、後註冊者在前。
對每個 guess × 每個 converter,先問 converter.accepts(file_stream, stream_info)
(通常用 mimetype / extension 判斷),接受後呼叫 converter.convert(...) 得到 Markdown。
任一 converter 拋例外時不會立刻失敗——被記錄為 FailedConversionAttempt,繼續試下一個。
成功後會把輸出做標準化:去掉每行行尾空白、把連續 3+ 個換行壓成 2 個。
全部 converter 都失敗時,若有失敗紀錄 → 拋 FileConversionException(內含所有嘗試);
完全沒人接受 → 拋 UnsupportedFormatException。
convert("report.pdf")
└─ 線索:副檔名 .pdf + magika 內容偵測
└─ guesses = [StreamInfo(.pdf), StreamInfo(magika 猜測)]
└─ 依 priority 排序 converters:[PDF, DOCX, XLSX, ..., PlainText(10), Html(10), Zip(10)]
└─ PDF.accepts() → True → PDF.convert() → Markdown 字串
└─ 標準化輸出 → DocumentConverterResult(markdown=..., title=...)這是 MarkItDown 最體貼的設計:某個 converter 在 convert() 中途拋例外時,
該例外被 except Exception 接住、包成 FailedConversionAttempt 存進清單,
file_stream 會 seek 回原位置,繼續試下一個 converter。
最後若全軍覆沒才統一拋 FileConversionException(attempts=...),方便你一次看到所有失敗原因。
for stream_info in stream_info_guesses + [StreamInfo()]:
for converter_registration in sorted_registrations:
converter = converter_registration.converter
if converter.accepts(file_stream, stream_info, **_kwargs):
try:
res = converter.convert(file_stream, stream_info, **_kwargs)
except Exception:
failed_attempts.append(FailedConversionAttempt(
converter=converter, exc_info=sys.exc_info()))
finally:
file_stream.seek(cur_pos)
if res is not None:
res.text_content = normalize(res.text_content)
return res
if failed_attempts:
raise FileConversionException(attempts=failed_attempts)
raise UnsupportedFormatException(...)
圖片與音訊的輸出「依賴本機工具或 LLM」:
exiftool;描述需要 llm_client + llm_model(multimodal LLM)。兩者都缺 → 輸出為空。exiftool;轉錄需要 SpeechRecognition(預設 Google Web API)。轉錄失敗的例外不一定被吞掉(見媒體案例)。Markdown 非常接近純文字、token 效率高,同時又能表達標題、清單、表格、連結等結構; 主流 LLM(如 GPT-4o)原生「會講」Markdown。這是它存在的根本理由。