139 lines
4.6 KiB
Python
Executable File
139 lines
4.6 KiB
Python
Executable File
#!/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()
|