#!/usr/bin/env python3 # /// script # dependencies = [ # "markitdown[all]>=0.0.1a4", # "requests>=2.31.0", # ] # /// """ Universal Document to Markdown Converter Powered by Microsoft MarkItDown. Supports PDF, DOCX, XLSX, PPTX, HTML, CSV, TXT, JSON, XML, images, and audio. Can take a local file path, a remote URL, or stdin. """ import argparse import os import shutil import subprocess import sys import tempfile import urllib.parse def ensure_dependencies_and_run(): """If markitdown is not installed in the current Python env, re-exec via uv run if available.""" try: import markitdown # noqa: F401 import requests # noqa: F401 except ImportError: uv_path = shutil.which("uv") if uv_path and os.environ.get("_MARKITDOWN_REEXEC") != "1": env = os.environ.copy() env["_MARKITDOWN_REEXEC"] = "1" cmd = [uv_path, "run", os.path.abspath(__file__), *sys.argv[1:]] try: res = subprocess.run(cmd, env=env) sys.exit(res.returncode) except Exception as e: sys.stderr.write(f"Failed to re-execute via uv: {e}\n") sys.stderr.write("Error: 'markitdown[all]' or 'requests' not found. Please install with:\n") sys.stderr.write(" uv pip install 'markitdown[all]' requests\n") sys.exit(1) ensure_dependencies_and_run() from markitdown import MarkItDown import requests def download_url(url: str, output_dir: str) -> str: """Download a remote URL to a temporary local file, guessing the filename from URL or headers.""" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" } resp = requests.get(url, headers=headers, stream=True, timeout=60) resp.raise_for_status() # Determine filename parsed = urllib.parse.urlparse(url) basename = os.path.basename(parsed.path) or "downloaded_file" if not os.path.splitext(basename)[1]: ct = resp.headers.get("Content-Type", "") if "pdf" in ct: basename += ".pdf" elif "word" in ct or "docx" in ct: basename += ".docx" elif "sheet" in ct or "excel" in ct or "xlsx" in ct: basename += ".xlsx" elif "html" in ct: basename += ".html" local_path = os.path.join(output_dir, basename) with open(local_path, "wb") as f: for chunk in resp.iter_content(chunk_size=65536): if chunk: f.write(chunk) return local_path def convert_file(path: str) -> str: """Convert local file to Markdown text using MarkItDown.""" md = MarkItDown() result = md.convert(path) return result.text_content def main(): parser = argparse.ArgumentParser( description="Convert documents (PDF, Office, HTML, images) to Markdown via Microsoft MarkItDown" ) parser.add_argument("file", nargs="?", help="Local path to the document file") parser.add_argument("--url", "-u", help="Remote URL of the document to download and convert") parser.add_argument("--output", "-o", help="Optional output file path (defaults to stdout)") parser.add_argument("--frontmatter", action="store_true", help="Add YAML frontmatter with file metadata") args = parser.parse_args() if not args.file and not args.url: parser.print_help() sys.exit(1) temp_dir = None target_file = args.file try: if args.url: temp_dir = tempfile.mkdtemp(prefix="markitdown_") target_file = download_url(args.url, temp_dir) if not target_file or not os.path.exists(target_file): sys.stderr.write(f"Error: File not found: {target_file}\n") sys.exit(1) content = convert_file(target_file) if args.frontmatter: meta = [ "---", f"source_file: {os.path.basename(target_file)}", f"original_url: {args.url or ''}", f"size_bytes: {os.path.getsize(target_file)}", "converted_by: microsoft-markitdown", "---", "", ] content = "\n".join(meta) + content if args.output: os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True) with open(args.output, "w", encoding="utf-8") as f: f.write(content) print(f"Successfully converted to {args.output}") else: print(content) finally: if temp_dir and os.path.exists(temp_dir): shutil.rmtree(temp_dir, ignore_errors=True) if __name__ == "__main__": main()