feat(skills): add markitdown document converter and regensburg-stadtrat civic intelligence skills
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user