DockerとPythonでMarkItDownをブラウザ上で使えるようにしてみる

# はじめに
先日、Microsoftが「MarkItDown」というツールを公開しました。このツールは、あらゆるフォーマットのファイルをマークダウン形式に変換してくれるツールです。
https://github.com/microsoft/markitdown
READMEを見てみると、コマンドラインで動かすことが想定されている。
```bash
markitdown path-to-file.pdf > document.md
markitdown path-to-file.pdf -o document.md
cat path-to-file.pdf | markitdown
```
チームメンバー全員が使い易いように、WEBにファイルをアップロードすればマークダウン形式に変換してくれるアプリを作成してみる。

# 本題
今回はDockerで環境構築をして、PythonライブラリのFlaskを用いてMarkItDownツールをブラウザで使えるようにしてみたいと思います。
ファイル構造は以下。
```
.
└── markitdown-app-docker
    ├── app.py
    ├── Dockerfile
    ├── docker-compose.yml
    └── uploads/
```

## Flaskで簡易的なアプリケーションを作成
以下のようにPythonライブラリのFlaskを利用して、簡易的なアプリケーションを作成しました。
アップロードしたファイルは一度UPLOAD_FOLDERに保存されます。（<-好み）
アップロードできるファイル拡張子をALLOWED_EXTENSIONSで制限しています。（<-好み）

```python:app.py
from flask import Flask, request, render_template_string
import os
import subprocess
from markitdown import MarkItDown

app = Flask(__name__)

# アップロードされたファイルの保存先ディレクトリ
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'html', 'docx', 'pdf', 'pptx', 'xlsm', 'csv', 'json', 'xml', 'zip'}  # マークダウンに変換したいファイル形式

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# アップロードされるファイルの拡張子をチェック
def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        # ファイルが選択されているかチェック
        if 'file' not in request.files:
            return 'No file part'
        file = request.files['file']
        if file.filename == '':
            return 'No selected file'

        # ファイルを保存
        if file and allowed_file(file.filename):
            filename = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
            file.save(filename)

            # markitdownツールでマークダウンに変換
            md = MarkItDown()
            md_render = md.convert(filename)
            md_content = md_render.text_content

            # 変換したマークダウン内容を表示
            return render_template_string("""
                <h1>Markdown Output</h1>
                <pre>{{ md_content }}</pre>
                <br>
                <a href="/">Go back</a>
            """, md_content=md_content)

    return '''
    <!doctype html>
    <title>Upload File</title>
    <h1>Convert to Markdown</h1>
    <form method="post" enctype="multipart/form-data">
      <input type="file" name="file">
      <input type="submit" value="Upload">
    </form>
    '''

if __name__ == '__main__':
    app.run(host="0.0.0.0")
```

## Dockerファイルの作成
Flaskで作成したアプリケーションを動作させるための環境をDockerで作成します。
基本的には、本家レポジトリの[Dockerfile](https://github.com/microsoft/markitdown/blob/main/Dockerfile)を基に、少し変更しています。

```Dockerfile:Dockerfile
FROM python:3.13-slim-bullseye

USER root

# 作業ディレクトリを設定
WORKDIR /app

# markitdownツールのインストール（Node.jsが必要）
RUN apt-get update && apt-get install -y --no-install-recommends \
    ffmpeg \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

RUN pip install markitdown Flask

# アプリケーションのコードをコピー
COPY ./app.py /app/

# アップロード用のディレクトリを作成
RUN mkdir -p /app/uploads

# ポート5000を開放
EXPOSE 5000

# Flaskアプリケーションを実行
CMD ["python", "app.py"]
```

docker-composeを使いたい人は、以下のようなyamlファイルを作成する。
他と被らないポートを設定してください（ここでは、5555番ポートを指定）
```Dockerfile:docker-compose.yml
services:
  app:
    build: .
    ports:
      - "5555:5000"
    volumes:
      - ./uploads:/app/uploads
    environment:
      - FLASK_ENV=development
```
## 完成
dockerコマンドでアプリケーションを立ち上げます。
```bash
docker compose up -d
```
その後、立ち上げたマシンにアクセスしてみる（例えば```0.0.0.0:5555```）
すると、以下のような画面が立ち上がるので、ファイルを指定してアップロードすると、マークダウン形式に変換されたテキストが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/300353/3027fe25-e9f4-0d1f-cd62-55663f04dfc0.png)


# 終わりに
年末なので、元々目指していた形からはかなり簡易的な出来上がりになった。少なくとも、ドラッグアンドドロップでファイルのアップロードができるように改良したいところ。あとは、gpt-4oを使うmarkitdownや、リアルタイムにマークダウンを共同編集できたりすると嬉しいかも。今後、時間があれば改良していくかもしれないし、改良した際には記事もアップデートするかもしれない。良いお年を。