packages/markitdown/src/markitdown/_markitdown.py(806 行)_markitdown.py 定義 MarkItDown 類別與 ConverterRegistration。
關鍵設計:5 個 convert_* 方法全部最終匯流到同一個 _convert()——
先猜出「這個檔案可能是什麼」的 StreamInfo 清單,再依 priority 依序嘗試每個 converter,直到有人成功。
開頭兩個模組級常數定義優先級:特定格式 0.0、通用兜底 10.0。
特定格式(docx/pdf/xlsx…)都給 0.0;「近乎兜底」的
PlainTextConverter / HtmlConverter / ZipConverter 給 10.0。
註冊順序的註解寫得很明確:「越特定的 converter 要放在越通用之下」。
用模組級全域 _plugins 記快取,第一次呼叫才真的
entry_points(group="markitdown.plugin")。單一外掛 load() 拋例外時
只 warn() 並跳過,不會中斷——這是「外掛壞了不影響主程式」的設計。
三件事:
Accept: text/markdown, text/html;q=0.9...,
伺服器支援時會直接回 Markdown。magika.Magika() 一次初始化,供型別猜測。enable_builtins 預設 None→True 註冊內建;
enable_plugins 預設關閉。*),避免位置參數錯位。先解析全域選項(llm_client / llm_model / llm_prompt / exiftool_path / style_map),
exiftool_path 依序來自 kwargs → EXIFTOOL_PATH 環境變數 → 白名單路徑
(/usr/bin、C:\Program Files…)搜尋。然後依註冊順序 register_converter(...) 加 22 個內建;
若提供 docintel_endpoint / cu_endpoint,再把對應 Azure converter
插到堆疊頂端(最優先)。重複呼叫會 warn()。
呼叫 _load_plugins(),對每個 plugin 執行
plugin.register_converters(self, **kwargs)——這就是外掛拿到
llm_client / llm_model 等全域選項的方式。
self._converters.insert(0, ConverterRegistration(...))。
因為「後註冊者在前」+ 穩定排序,同優先級時最後註冊的最先試——外掛可以藉此覆蓋內建。
register_page_converter() 是棄用別名,僅 warn()。
最通用的入口:str 以 http:/https:/file:/data: 前綴判斷走
convert_uri 或 convert_local;Path → local;
requests.Response → response;有 .read() 的 bytes 流 → stream。
否則拋 TypeError。legacy url= kwarg 會被改名成 mock_url。
以副檔名 + 檔名建 base_guess,用 kwargs/stream_info 補強,open 成 bytes 流後
_get_stream_info_guesses() 猜測 → _convert()。安全文件建議「只讀本機檔就用這個」。
不可 seek() 的串流會先讀成 BytesIO buffer 再 seek(0),
確保 magika 能重複讀取。
file: → file_uri_to_path 解出 path;netloc 非空且非 localhost 會 ValueError。data: → parse_data_uri 解出 mimetype + bytes → convert_stream。http(s): → 用 session 抓(raise_for_status())→ convert_response。convert_url() 是它的別名(未來可能棄用)。
從 content-type 拆 mimetype + charset,從 content-disposition 的
filename= 或 URL path 的副檔名湊出 extension,組 base_guess 後轉換。
這讓「自己控制 HTTP 抓取」成為可能。
流程(每個 guess × 每個 converter):
sorted(self._converters, key=lambda x: x.priority)——priority 可能變動故每次重排。converter.accepts(...),NotImplementedError 視為不接受。converter.convert(...);拋例外 → 記 FailedConversionAttempt,finally 把 stream 跳回原位。rstrip() + \n{3,} 壓成 \n\n,回傳。全部失敗:有失敗紀錄 → FileConversionException(attempts);沒人接受 →
UnsupportedFormatException。
_convert 裡「複製進 kwargs」傳給每個 converter——所以 CLI 的 --llm-client 能下推到所有 converter。mimetypes.guess_type 補;反之互補。magika.identify_stream() 讀位元組猜型別,文字類再讀前 4KB 用
charset_normalizer 猜編碼。避免「utf-8」vs「UTF-8」vs「utf8」的比對陷阱;查不到就原樣回傳。