mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-08 00:05:37 +00:00
137 lines
3.6 KiB
Python
137 lines
3.6 KiB
Python
# /// script
|
|
# requires-python = ">=3.12"
|
|
# dependencies = ["markitdown[pdf,pptx,docx,xlsx]>=0.1.5,<0.2", "python-docx>=1.1,<2", "click>=8.3,<9"]
|
|
# ///
|
|
"""Universal document to Markdown converter.
|
|
|
|
Converts .docx, .xlsx, .pptx, .pdf, .html, .ipynb, .xml, .rss, .zip, .msg
|
|
and other supported formats to Markdown using the markitdown library.
|
|
|
|
Usage:
|
|
uv run markitdown_cli.py [OPTIONS] FILE
|
|
"""
|
|
|
|
import sys
|
|
import warnings
|
|
from pathlib import Path
|
|
|
|
# Suppress pydub/ffmpeg warning from markitdown[all] — irrelevant for document conversion
|
|
warnings.filterwarnings("ignore", message="Couldn't find ffmpeg", category=RuntimeWarning)
|
|
|
|
import click
|
|
|
|
|
|
SUPPORTED_EXTENSIONS = {
|
|
".docx",
|
|
".xlsx",
|
|
".pptx",
|
|
".pdf",
|
|
".html",
|
|
".htm",
|
|
".ipynb",
|
|
".xml",
|
|
".rss",
|
|
".atom",
|
|
".zip",
|
|
".msg",
|
|
".eml",
|
|
".csv",
|
|
".tsv",
|
|
".json",
|
|
".txt",
|
|
".md",
|
|
".rst",
|
|
".rtf",
|
|
".jpg",
|
|
".jpeg",
|
|
".png",
|
|
".gif",
|
|
".bmp",
|
|
".tiff",
|
|
".wav",
|
|
".mp3",
|
|
}
|
|
|
|
|
|
PLAIN_TEXT_EXTENSIONS = {".txt", ".md", ".rst", ".csv", ".tsv", ".json", ".xml", ".html", ".htm", ".log"}
|
|
|
|
|
|
def _extract_docx_text(file_path: Path) -> str:
|
|
"""Fallback DOCX extraction that avoids MarkItDown native dependency chain."""
|
|
from docx import Document
|
|
|
|
doc = Document(str(file_path))
|
|
parts: list[str] = []
|
|
|
|
for p in doc.paragraphs:
|
|
if p.text:
|
|
parts.append(p.text)
|
|
|
|
for table in doc.tables:
|
|
for row in table.rows:
|
|
cells = [cell.text.strip() for cell in row.cells]
|
|
parts.append(" | ".join(cells))
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def write_output(text: str, output_path: str | None) -> None:
|
|
"""Write text to file or stdout."""
|
|
if output_path:
|
|
Path(output_path).write_text(text, encoding="utf-8")
|
|
click.echo(f"Output written to {output_path}", err=True)
|
|
else:
|
|
click.echo(text)
|
|
|
|
|
|
@click.command()
|
|
@click.argument("file", type=click.Path(exists=True, dir_okay=False))
|
|
@click.option("-o", "--output", type=click.Path(), default=None, help="Write output to file instead of stdout.")
|
|
def main(file: str, output: str | None) -> None:
|
|
"""Convert a document to Markdown.
|
|
|
|
Supports .docx, .xlsx, .pptx, .pdf, .html, .ipynb, .xml, .rss, .zip, .msg,
|
|
and many other formats.
|
|
"""
|
|
file_path = Path(file)
|
|
ext = file_path.suffix.lower()
|
|
|
|
if ext not in SUPPORTED_EXTENSIONS:
|
|
click.echo(
|
|
f"Warning: extension '{ext}' may not be supported. Attempting conversion anyway.",
|
|
err=True,
|
|
)
|
|
|
|
# Fast fallback for plain text-like inputs.
|
|
if ext in PLAIN_TEXT_EXTENSIONS:
|
|
text = file_path.read_text(encoding="utf-8", errors="replace")
|
|
write_output(text, output)
|
|
return
|
|
|
|
# DOCX fallback path that avoids MarkItDown's optional native stack.
|
|
if ext == ".docx":
|
|
try:
|
|
write_output(_extract_docx_text(file_path), output)
|
|
return
|
|
except Exception:
|
|
# Fall back to MarkItDown path below if python-docx extraction fails unexpectedly.
|
|
pass
|
|
|
|
try:
|
|
from markitdown import MarkItDown # Lazy import: avoids startup crashes on systems missing optional native deps
|
|
|
|
converter = MarkItDown()
|
|
result = converter.convert(str(file_path))
|
|
write_output(result.text_content or "", output)
|
|
except Exception as e:
|
|
click.echo(
|
|
f"Error converting {file_path.name}: {e}\n"
|
|
"If this is a native dependency issue (e.g. onnxruntime DLL), "
|
|
"install Microsoft Visual C++ Redistributable and retry.",
|
|
err=True,
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|