Files
obsidian-remarkable/src/utils/zip.ts
T
fegger 0211cf33f3 ```
Add handwriting OCR pipeline and update documentation

- Introduce full OCR workflow: HWR extraction → PNG rendering → GLM-OCR → Ollama refinement
- Add new modules for page rendering, GLM-OCR client, style refinement, and ZIP utilities
- Update settings with drawj2d path and batch size controls
- Document Phase 2 completion and known limitations in IMPLEMENTATION.md
- Validate dependencies and handle cross-platform temp directory cleanup
  ```
2026-05-31 14:34:38 +02:00

65 lines
2.3 KiB
TypeScript

import { runCommand } from "./process";
/**
* Extract a .rm file (which is a zip archive) to a temporary directory.
* Returns the path to the extraction directory.
*/
export async function extractRmFile(rmPath: string, outputDir: string): Promise<string> {
// .rm files are zip archives
const { code, stderr } = await runCommand("unzip", ["-o", rmPath, "-d", outputDir]);
if (code !== 0) {
throw new Error(`Failed to extract .rm file: ${stderr}`);
}
return outputDir;
}
/**
* List the contents of the extracted .rm archive.
*/
export async function listRmContents(extractedDir: string): Promise<string[]> {
const { stdout, code, stderr } = await runCommand("find", [extractedDir, "-type", "f"]);
if (code !== 0) {
throw new Error(`Failed to list .rm contents: ${stderr}`);
}
return stdout.trim().split("\n").filter(Boolean);
}
/**
* Check if a document is a notebook (handwritten) or a PDF/ebook.
* On reMarkable v3.x, notebooks have .rm page files and no content.pdf.
* Falls back to rendering a test page if ambiguous.
*/
export async function isNotebook(extractedDir: string): Promise<boolean> {
const contents = await listRmContents(extractedDir);
// Notebooks have .rm page files and no embedded PDF content
const hasRmPages = contents.some((f) => f.endsWith(".rm") && !f.endsWith(".zip"));
const hasContentPdf = contents.some((f) => f.endsWith("content.pdf") || f.endsWith(".pdf"));
// Clear case
if (hasRmPages && !hasContentPdf) return true;
if (!hasRmPages || hasContentPdf) return false;
// Ambiguous: try to render a page
const pageFiles = contents.filter((f) => f.endsWith(".rm") && !f.endsWith(".zip"));
if (pageFiles.length === 0) return false;
try {
// Try rendering the first page
const { code } = await runCommand("file", [pageFiles[0]]);
// If file command succeeds and identifies it as data, assume notebook
return code === 0;
} catch {
return false;
}
}
/**
* Get the list of page .rm files from an extracted notebook.
* Returns basenames only.
* Recursively searches for .rm files (not just root).
*/
export async function getPageFiles(extractedDir: string): Promise<string[]> {
const contents = await listRmContents(extractedDir);
return contents.filter((f) => f.endsWith(".rm") && !f.endsWith(".zip")).sort();
}