I reverse-engineering the closed GoodNotes format using LLMs Kaih1825 released an open-source Python toolkit that decodes GoodNotes 5 and 6 .goodnotes archives by parsing protobuf wire format, Apple LZ4 framed streams, and Troy Hanson TPL memory images, exporting strokes to JSON and SVG. The project, built entirely via AI-assisted 'vibe coding' with Gemini 3.1 Pro, Gemini 3.6 Flash, and Claude Sonnet 5, aims to free user data from GoodNotes' proprietary format and enable migration to open vector formats like InkML. Warning Vibe Coding Disclaimer : This entire project was developed through Vibe Coding AI-assisted rapid pair-programming and exploratory development . While the parser has been verified against test corpora, code and architecture choices reflect an experimental AI-driven iteration style. Use at your own discretion An independent, open-source, fully typed Python toolkit for inspecting and parsing user-supplied GoodNotes 5 and 6 .goodnotes archives. It decodes protobuf wire format directly, parses Apple LZ4 framed streams, decodes Troy Hanson TPL memory images, extracts observed RGBA stroke data, and exports documents to JSON and SVG. This project is not affiliated with, endorsed by, sponsored by, or officially connected to Goodnotes Limited. See LEGAL-NOTICE.md /Kaih1825/parser-for-goodnotes/blob/main/LEGAL-NOTICE.md for release and usage notes. It deliberately does NOT use heuristic float scanning . Rendering Comparison %EF%B8%8F-rendering-comparison Motivation & Under the Hood motivation--under-the-hood Features features Installation & Setup installation--setup CLI Usage cli-usage Python Library API python-library-api Documentation documentation | Source Archive | GoodNotes Original .jpg | This Project SVG Export .svg | |---|---|---| Example 1: Handwritten Formulas & Images ex1.goodnotes | || Example 2: Brush Styles & Stroke Variations ex2.goodnotes | || Example 3: Multi-Layer Images & Chinese Text ex3.goodnotes | GoodNotes is an amazing note-taking app, but its closed ecosystem has always been a pain point. If you want to export your notebooks while keeping the vector strokes editable, you're pretty much out of luck—you either have to stick with the proprietary .goodnotes file format or export to a flattened PDF that loses editability. To solve this, I built an open-source parser that can decode .goodnotes files. I have zero background in reverse engineering, and decoding this format wasn't easy I haven't seen many successful projects tackling this . So, I built this entirely through "vibe coding" using LLMs—primarily Gemini 3.1 Pro, Gemini 3.6 Flash, and Claude Sonnet 5. Here is how the parsing works under the hood: ZIP & Protobuf: After some analysis, it turns out that .goodnotes is essentially a ZIP archive. The main stroke data is stored page by page in the notes/ directory as serialized Protobuf files. Since I didn't have the official .proto schemas, the project blindly parses the Protobuf via the underlying Wire Format to construct an Abstract Syntax Tree AST . Apple LZ4: Next, we discovered that some data fields start with bv41 or bv4- and end with bv4$ . This is a signature for Apple's proprietary Framed LZ4 compression. We successfully decompressed this by maintaining a 64KB sliding history window and using bitwise operations to handle the LZ4 tokens. Troy Hanson's TPL: The decompressed plaintext starts with the magic bytes tpl\0 , revealing it as Troy Hanson's TPL format a C serialization library . By inferring the format strings, we were able to extract the raw stroke data—which consists of discrete points containing pressure values. SVG Ribbons: Finally, to render the strokes with natural variable widths, the parser calculates the normal vectors between adjacent points and applies a sliding average for smoothing. It then pushes the edges outward based on the pressure values, stitching the discrete points into a closed SVG polygon ribbon. Currently, the project can parse the binary files inside a .goodnotes archive to extract strokes, text, and other elements, exporting them directly to .svg or .pdf . Unlike standard exports from the GoodNotes app itself, this parser completely unlocks the raw data. The ultimate goal is to allow conversions to other open vector formats like InkML so users can migrate to other apps and prevent their hard work from being locked in by a single vendor. For more detailed parsing principles, please refer to the GitHub Wiki https://github.com/Kaih1825/document-parser-for-goodnotes/wiki . Protobuf Wire Decoder : Lossless parsing of framed and unframed protobuf messages, preserving unknown fields. Apple LZ4 & Troy Hanson TPL Decoder : Decompresses bv41 LZ4 streams and decodes embedded TPL format strings vuA v A S uu ... into structured stroke points and variable-width ribbons. Stroke & Color Parsing : Direct protobuf trailer decoding after bv4$ to extract exact RGBA colors and highlighter transparency. Stroke Support : Parses a growing set of dots, lines, curves, pen tools, highlighters, erasers, shapes, and moved/copied elements observed in the test corpus. Page & Background Resolution : Automatic PDF background /MediaBox dimension detection A4, Letter, landscape vs. portrait , page ordering, text fragments, and sticky notes 便條紙 content extraction. CLI Tools : gn-inspect , gn-dump , gn-diff , gn-export-json , gn-export-svg , gn-export-pdf . Fountain Pen, Ballpoint Pen, Brush Pen, Highlighter : Full support with custom colors and line thicknesses. Text : Typed text fragments. Sticky Notes : Sticky note contents and formatting. Images : Embedded images. Auto-Shapes & Shapes : Vector shape rendering. Eraser : Eraser strokes and line cuts. Lasso Tool : Transformed, copied, or moved elements. PDF-backed Notebooks : PDF background pages with dimensions /MediaBox . Pencil : Visible output, but pressure sensitivity is incorrect and tilt sensitivity is missing. Arrows : Render output is currently highly unstable. Stickers / Elements : Certain vector lines/strokes may fail to export. Audio Recordings : Not yet implemented. Using uv https://github.com/astral-sh/uv : uv sync Or standard pip install: pip install -e . Run the unit test suite with: uv run pytest Note: samples/ is intentionally ignored and is not part of the public source tree. Do not publish .goodnotes files or extracted assets unless you have permission to redistribute them. Inspect archive inventory and sha256 checksums gn-inspect sample.goodnotes Lossless dump of any protobuf member to JSON gn-dump sample.goodnotes index.notes.pb Diff two .goodnotes archives gn-diff before.goodnotes after.goodnotes Export entire document, metadata, pages, strokes, and raw wire trees to JSON gn-export-json sample.goodnotes -o document.json Export vector SVG pages with exact stroke ribbons, colors, and dimensions gn-export-svg sample.goodnotes -o pages-svg Export vector SVGs and package all pages into a PDF gn-export-svg sample.goodnotes -o pages-svg --pdf Directly export multi-page PDF document gn-export-pdf sample.goodnotes -o document.pdf Or via module invocation: PYTHONPATH=src python3 -m goodnotes re.cli export-svg sample.goodnotes -o pages-svg For the full CLI reference including all flags and batch export examples , see cli.md /Kaih1825/parser-for-goodnotes/blob/main/cli.md english . python from goodnotes re import GoodNotesDocument with GoodNotesDocument.open "sample.goodnotes" as doc: Inventory members = doc.inventory Document pages with strokes, dimensions, and text pages = doc.pages for page in pages: print f"Page {page.index + 1}: {page.dimensions.width}x{page.dimensions.height} pt" print f"Strokes: {len page.strokes }" for stroke in page.strokes: print f" Stroke {stroke.uuid}: color {stroke.color hex}, alpha {stroke.alpha}, points {len stroke.points }" Text and Sticky Notes content fragments = doc.text fragments for frag in fragments: print f" {frag.source path} {frag.format}: {frag.text}" Structural page-element summaries for format analysis for page in pages: for element in page.elements: print element.kind, element.uuid, element.attachment uuid, element.related uuids | Document | Description | |---|---| cli.md | GitHub Wiki https://github.com/Kaih1825/document-parser-for-goodnotes/wiki LEGAL-NOTICE.md See also the Contributing Guide /Kaih1825/parser-for-goodnotes/blob/main/CONTRIBUTING.md . Important Legal & Trademark Notice : "Goodnotes" and related names, logos, and marks are the property of Goodnotes Limited. Document Parser for GoodNotes is an independent, community-developed project and is not affiliated with, endorsed by, sponsored by, or officially connected to Goodnotes Limited . For full legal, trademark, privacy, and redistribution details, please read LEGAL-NOTICE.md /Kaih1825/parser-for-goodnotes/blob/main/LEGAL-NOTICE.md . Warning Vibe Coding 免責聲明 :本專案完全採用 Vibe Coding (AI 輔助快速結對程式設計與探索式開發)進行構建。雖然解析器已通過測試樣本驗證,但程式碼結構與架構選擇體現了 AI 驅動的實驗性疊代風格。請自行評估並謹慎使用! 一套獨立、開源且完整型別化的 Python 工具組,用於檢視與解析使用者提供的 GoodNotes 5 與 GoodNotes 6 .goodnotes 封存檔。它可直接解碼 protobuf wire format 、解析 Apple LZ4 框架串流、解碼 Troy Hanson TPL 記憶體映像、擷取觀察到的 RGBA 筆跡資料,並將文件匯出為 JSON 與 SVG。 本專案與 Goodnotes Limited 沒有任何關聯、背書、贊助或官方合作關係。 發布與使用注意事項請參閱 LEGAL-NOTICE.md /Kaih1825/parser-for-goodnotes/blob/main/LEGAL-NOTICE.md 。 本專案 刻意不使用啟發式浮點數掃描 。 | 原始封存檔 | GoodNotes 原版渲染 .jpg | 本專案 SVG 匯出 .svg | |---|---|---| 範例 1:手寫公式與插圖 ex1.goodnotes | || 範例 2:多款筆刷與色彩筆跡 ex2.goodnotes | || 範例 3:多層圖文疊加與中文手寫 ex3.goodnotes | GoodNotes 是一個很棒的筆記軟體,但由於其封閉性,除了匯出成封閉的 .goodnotes 專屬檔案,或是會失去編輯能力的 PDF 之外,幾乎沒有其他保留向量筆跡的選擇,因此我開發了一個解析工具來解析 .goodnotes 檔案。 因為我對這類工程沒有經驗,且解析此 .goodnotes 不是那麼容易(目前沒有看到幾個有成功解析出來的專案),因此我使用 vibe coding 主要是 Gemini 3.1 Pro, Gemini 3.6 Flash 及 Claude Sonnet 5 來完成這個專案。 以下是解析原理: 壓縮與 Protobuf 盲解: 經過分析,得知 .goodnotes 本質上是一個壓縮檔,而主要的筆跡資訊以頁為單位存在 notes/ 下,各頁面的筆跡資訊存為 Protobuf 序列化檔案。在這個專案中,使用了 Wire Format 的方式來解析,將其構建成抽象語法樹。 Apple LZ4 逆向: 接著,我們發現了部分欄位以 bv41 或 bv4- 開頭,並且以 bv4$ 結尾,代表這是經過 Apple LZ4 壓縮的資料,我們利用維護 64KB 的歷史滑動視窗,靠位元運算處理 LZ4 的 token 來將其成功解壓。 TPL 記憶體映像: 解壓後的明文以 tpl\0 開頭,因此得知他是 Troy Hanson's TPL 格式,並透過格式字串推導出他的筆跡資訊(帶有壓感的離散點)。 向量幾何重建: 接著,透過計算相鄰兩點間的法向量並進行滑動平均平滑化,再結合壓感值向外推移,就能將離散點縫合成封閉的多邊形,最終輸出為帶有自然粗細變化的 SVG 向量筆跡。 目前,專案可以分析 .goodnotes 中的二進制檔案來獲取筆跡、文字等資訊,並輸出成 .svg 或 .pdf 格式。與直接從 GoodNotes app 匯出不同,本專案因為解析了 .goodnotes 格式,因此未來完全可以將解析出的資料,轉換為其他開源格式(如 InkML),讓使用者的心血不再被單一廠商綁架。 詳細的解析原理可以前往 GitHub Wiki https://github.com/Kaih1825/document-parser-for-goodnotes/wiki 區查看。 Protobuf Wire Decoder :無損解析有框架與無框架的 protobuf 訊息,並保留未知欄位。 Apple LZ4 與 Troy Hanson TPL Decoder :解壓縮 bv41 LZ4 串流,並將嵌入的 TPL 格式字串解碼為結構化筆跡點與可變寬度筆跡帶。 筆跡與色彩解析 :解析 bv4$ 後的 protobuf trailer,以擷取精確 RGBA 顏色與螢光筆透明度。 筆跡與幾何元件支援 :解析測試樣本中觀察到的單點、直線、曲線、鋼筆/原子筆工具、螢光筆、橡皮擦切口、圖形及移動/複製元素。 頁面與背景解析 :自動偵測 PDF /MediaBox 尺寸(A4、Letter、橫向與直向)、頁面順序、文字片段與便條紙內容。 CLI 工具 : gn-inspect 、 gn-dump 、 gn-diff 、 gn-export-json 、 gn-export-svg 、 gn-export-pdf 。 鋼筆、原子筆、畫筆、螢光筆 :可處理不同顏色及粗細。 文字 :打字文字內容。 便利貼 :便條紙內容。 圖片 :內嵌圖片。 自動形狀、形狀 :幾何圖形。 橡皮擦 :橡皮擦筆跡與切口。 套索工具 :選取、移動與變形的元素。 有 PDF 的筆記本 :PDF 背景與頁面尺寸。 鉛筆 :可顯示,但壓感有問題,以及沒有傾斜感知。 箭頭 :目前輸出極度不穩定。 素材(貼紙) :可能有部分線條無法輸出。 錄音 :還沒實作。 使用 uv https://github.com/astral-sh/uv : uv sync 或使用標準 pip: pip install -e . 執行測試: uv run pytest 注意: samples/ 刻意被忽略,且不屬於公開原始碼樹。除非您擁有重新發布的權限,否則請勿發布 .goodnotes 檔案或擷取出的資產。 檢視封存檔目錄清單與 sha256 校驗碼 gn-inspect sample.goodnotes 無損印出任何 protobuf 成員至 JSON gn-dump sample.goodnotes index.notes.pb 比對兩個 .goodnotes 封存檔差異 gn-diff before.goodnotes after.goodnotes 匯出整份文件、元資料、頁面、筆跡與原始 wire 樹狀圖至 JSON gn-export-json sample.goodnotes -o document.json 匯出包含精確筆跡緞帶、顏色與尺寸的向量 SVG 頁面 gn-export-svg sample.goodnotes -o pages-svg 匯出向量 SVG 並同步按頁面順序打包為 PDF gn-export-svg sample.goodnotes -o pages-svg --pdf 直接將整份筆記匯出為多頁 PDF 文件 gn-export-pdf sample.goodnotes -o document.pdf 完整 CLI 參考請參閱 cli.md /Kaih1825/parser-for-goodnotes/blob/main/cli.md %E4%B8%AD%E6%96%87 。 python from goodnotes re import GoodNotesDocument with GoodNotesDocument.open "sample.goodnotes" as doc: 目錄清單 members = doc.inventory 包含筆跡、尺寸與文字的文件頁面 pages = doc.pages for page in pages: print f"Page {page.index + 1}: {page.dimensions.width}x{page.dimensions.height} pt" print f"筆跡數量: {len page.strokes }" for stroke in page.strokes: print f" Stroke {stroke.uuid}: color {stroke.color hex}, alpha {stroke.alpha}, points {len stroke.points }" 打字文字與便條紙內容 fragments = doc.text fragments for frag in fragments: print f" {frag.source path} {frag.format}: {frag.text}" 用於格式分析的結構化頁面元素摘要 for page in pages: for element in page.elements: print element.kind, element.uuid, element.attachment uuid, element.related uuids | 文件 | 說明 | |---|---| cli.md | GitHub Wiki https://github.com/Kaih1825/document-parser-for-goodnotes/wiki LEGAL-NOTICE.md 另請參閱 貢獻指南 /Kaih1825/parser-for-goodnotes/blob/main/CONTRIBUTING.md 。 Important 法律與商標聲明 :「Goodnotes」及相關名稱、標誌與標誌均為 Goodnotes Limited 所有。Document Parser for GoodNotes 是一套獨立且由社群開發的開源專案, 與 Goodnotes Limited 沒有任何附屬、背書、贊助或官方合作關係 。完整法律、商標、隱私及重新發布注意事項,請參閱 LEGAL-NOTICE.md /Kaih1825/parser-for-goodnotes/blob/main/LEGAL-NOTICE.md 。