diff --git a/.gitignore b/.gitignore index aa4abd38..2c651be8 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,7 @@ cython_debug/ src/.DS_Store .DS_Store .cursorrules + +# MarkItDown Batch Converter - User files +uploads/ +outputs/ diff --git a/BATCH_CONVERTER_README.md b/BATCH_CONVERTER_README.md new file mode 100644 index 00000000..84fe2df3 --- /dev/null +++ b/BATCH_CONVERTER_README.md @@ -0,0 +1,130 @@ +# MarkItDown Batch Converter Web Interface + +A modern web-based batch converter for MarkItDown with real-time notifications and drag-and-drop functionality. + +## 🌟 Features + +- **Modern Web UI**: Clean, responsive interface built with TailwindCSS +- **Drag & Drop**: Intuitive file upload with visual feedback +- **Batch Processing**: Convert multiple files simultaneously +- **Real-Time Updates**: Server-Sent Events (SSE) for instant completion notifications +- **Progress Tracking**: Live progress bars and status indicators +- **File Management**: Easy queue management with download/delete options +- **Toast Notifications**: Elegant user feedback system +- **Dark/Light Theme**: Automatic theme detection + +## 🚀 Quick Start + +### Prerequisites +- Python 3.8+ +- Flask +- MarkItDown library + +### Installation + +1. Install dependencies: +```bash +pip install -r requirements-server.txt +``` + +2. Start the server: +```bash +python server.py +``` + +3. Open your browser to: `http://localhost:5000` + +## 🏗️ Architecture + +### Backend (Flask) +- **RESTful API** for file operations +- **Server-Sent Events** for real-time notifications +- **Background threading** for non-blocking conversions +- **Secure file handling** with proper validation + +### Frontend (Vanilla JS) +- **EventSource API** for SSE connection +- **Responsive design** with TailwindCSS +- **Real-time UI updates** without polling +- **Progressive enhancement** approach + +## 📡 Real-Time System + +Instead of traditional polling, this implementation uses **Server-Sent Events (SSE)**: + +```javascript +// Frontend listens for real-time updates +eventSource.addEventListener('status_update', function(e) { + const data = JSON.parse(e.data); + updateFileStatus(data); // Instant UI update +}); +``` + +```python +# Backend sends immediate notifications +def notify_status_change(file_id, status): + broadcast_notification('status_update', { + 'file_id': file_id, + 'status': status + }) +``` + +## 🎯 Benefits Over Polling + +| Aspect | Polling | SSE (This Implementation) | +|--------|---------|---------------------------| +| **Latency** | 200-500ms delay | Instant (0ms) | +| **Server Load** | High (constant requests) | Low (push-only) | +| **Bandwidth** | Wasteful | Efficient | +| **User Experience** | Delayed updates | Real-time feedback | + +## 📁 File Structure + +``` +├── server.py # Flask backend with SSE +├── code.html # Web interface +├── requirements-server.txt # Python dependencies +├── start_converter.bat # Windows startup script +├── uploads/ # Temporary file storage +└── outputs/ # Converted markdown files +``` + +## 🔧 API Endpoints + +- `GET /` - Serve web interface +- `POST /api/upload` - Upload files +- `POST /api/convert` - Start batch conversion +- `GET /api/status` - Get current job status +- `GET /api/events` - SSE endpoint for real-time updates +- `GET /api/download/` - Download converted file +- `DELETE /api/delete/` - Remove file from queue + +## 🎨 UI Components + +- **File Drop Zone**: Visual drag-and-drop area +- **Conversion Queue**: Real-time file status list +- **Progress Bar**: Overall conversion progress +- **Toast Notifications**: Success/error messages +- **Live Indicator**: Connection status display + +## 🔒 Security Features + +- **Secure filename handling** with werkzeug +- **File size limits** (100MB max) +- **Input validation** on all endpoints +- **Error handling** with proper HTTP status codes + +## 🚀 Performance Optimizations + +- **Background threading** for non-blocking conversions +- **SSE connection pooling** for multiple clients +- **Efficient file handling** with streaming +- **Auto-cleanup** of temporary files + +## 🤝 Contributing + +This batch converter extends MarkItDown's capabilities while maintaining compatibility with the core library. It adds a user-friendly web interface without modifying the original conversion logic. + +## 📄 License + +Follows the same license as the main MarkItDown project. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index f9ba8cf6..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,9 +0,0 @@ -# Microsoft Open Source Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -Resources: - -- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/README.md b/README.md index 652afc05..84fe2df3 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,130 @@ -# MarkItDown +# MarkItDown Batch Converter Web Interface -[![PyPI](https://img.shields.io/pypi/v/markitdown.svg)](https://pypi.org/project/markitdown/) -![PyPI - Downloads](https://img.shields.io/pypi/dd/markitdown) -[![Built by AutoGen Team](https://img.shields.io/badge/Built%20by-AutoGen%20Team-blue)](https://github.com/microsoft/autogen) +A modern web-based batch converter for MarkItDown with real-time notifications and drag-and-drop functionality. -> [!TIP] -> MarkItDown now offers an MCP (Model Context Protocol) server for integration with LLM applications like Claude Desktop. See [markitdown-mcp](https://github.com/microsoft/markitdown/tree/main/packages/markitdown-mcp) for more information. +## 🌟 Features -> [!IMPORTANT] -> Breaking changes between 0.0.1 to 0.1.0: -> * Dependencies are now organized into optional feature-groups (further details below). Use `pip install 'markitdown[all]'` to have backward-compatible behavior. -> * convert\_stream() now requires a binary file-like object (e.g., a file opened in binary mode, or an io.BytesIO object). This is a breaking change from the previous version, where it previously also accepted text file-like objects, like io.StringIO. -> * The DocumentConverter class interface has changed to read from file-like streams rather than file paths. *No temporary files are created anymore*. If you are the maintainer of a plugin, or custom DocumentConverter, you likely need to update your code. Otherwise, if only using the MarkItDown class or CLI (as in these examples), you should not need to change anything. +- **Modern Web UI**: Clean, responsive interface built with TailwindCSS +- **Drag & Drop**: Intuitive file upload with visual feedback +- **Batch Processing**: Convert multiple files simultaneously +- **Real-Time Updates**: Server-Sent Events (SSE) for instant completion notifications +- **Progress Tracking**: Live progress bars and status indicators +- **File Management**: Easy queue management with download/delete options +- **Toast Notifications**: Elegant user feedback system +- **Dark/Light Theme**: Automatic theme detection -MarkItDown is a lightweight Python utility for converting various files to Markdown for use with LLMs and related text analysis pipelines. To this end, it is most comparable to [textract](https://github.com/deanmalmgren/textract), but with a focus on preserving important document structure and content as Markdown (including: headings, lists, tables, links, etc.) While the output is often reasonably presentable and human-friendly, it is meant to be consumed by text analysis tools -- and may not be the best option for high-fidelity document conversions for human consumption. +## 🚀 Quick Start -MarkItDown currently supports the conversion from: +### Prerequisites +- Python 3.8+ +- Flask +- MarkItDown library -- PDF -- PowerPoint -- Word -- Excel -- Images (EXIF metadata and OCR) -- Audio (EXIF metadata and speech transcription) -- HTML -- Text-based formats (CSV, JSON, XML) -- ZIP files (iterates over contents) -- Youtube URLs -- EPubs -- ... and more! - -## Why Markdown? - -Markdown is extremely close to plain text, with minimal markup or formatting, but still -provides a way to represent important document structure. Mainstream LLMs, such as -OpenAI's GPT-4o, natively "_speak_" Markdown, and often incorporate Markdown into their -responses unprompted. This suggests that they have been trained on vast amounts of -Markdown-formatted text, and understand it well. As a side benefit, Markdown conventions -are also highly token-efficient. - -## Prerequisites -MarkItDown requires Python 3.10 or higher. It is recommended to use a virtual environment to avoid dependency conflicts. - -With the standard Python installation, you can create and activate a virtual environment using the following commands: - -```bash -python -m venv .venv -source .venv/bin/activate -``` - -If using `uv`, you can create a virtual environment with: +### Installation +1. Install dependencies: ```bash -uv venv --python=3.12 .venv -source .venv/bin/activate -# NOTE: Be sure to use 'uv pip install' rather than just 'pip install' to install packages in this virtual environment +pip install -r requirements-server.txt ``` -If you are using Anaconda, you can create a virtual environment with: - +2. Start the server: ```bash -conda create -n markitdown python=3.12 -conda activate markitdown +python server.py ``` -## Installation - -To install MarkItDown, use pip: `pip install 'markitdown[all]'`. Alternatively, you can install it from the source: +3. Open your browser to: `http://localhost:5000` -```bash -git clone git@github.com:microsoft/markitdown.git -cd markitdown -pip install -e 'packages/markitdown[all]' -``` +## 🏗️ Architecture -## Usage +### Backend (Flask) +- **RESTful API** for file operations +- **Server-Sent Events** for real-time notifications +- **Background threading** for non-blocking conversions +- **Secure file handling** with proper validation -### Command-Line +### Frontend (Vanilla JS) +- **EventSource API** for SSE connection +- **Responsive design** with TailwindCSS +- **Real-time UI updates** without polling +- **Progressive enhancement** approach -```bash -markitdown path-to-file.pdf > document.md -``` +## 📡 Real-Time System -Or use `-o` to specify the output file: +Instead of traditional polling, this implementation uses **Server-Sent Events (SSE)**: -```bash -markitdown path-to-file.pdf -o document.md -``` - -You can also pipe content: - -```bash -cat path-to-file.pdf | markitdown -``` - -### Optional Dependencies -MarkItDown has optional dependencies for activating various file formats. Earlier in this document, we installed all optional dependencies with the `[all]` option. However, you can also install them individually for more control. For example: - -```bash -pip install 'markitdown[pdf, docx, pptx]' -``` - -will install only the dependencies for PDF, DOCX, and PPTX files. - -At the moment, the following optional dependencies are available: - -* `[all]` Installs all optional dependencies -* `[pptx]` Installs dependencies for PowerPoint files -* `[docx]` Installs dependencies for Word files -* `[xlsx]` Installs dependencies for Excel files -* `[xls]` Installs dependencies for older Excel files -* `[pdf]` Installs dependencies for PDF files -* `[outlook]` Installs dependencies for Outlook messages -* `[az-doc-intel]` Installs dependencies for Azure Document Intelligence -* `[audio-transcription]` Installs dependencies for audio transcription of wav and mp3 files -* `[youtube-transcription]` Installs dependencies for fetching YouTube video transcription - -### Plugins - -MarkItDown also supports 3rd-party plugins. Plugins are disabled by default. To list installed plugins: - -```bash -markitdown --list-plugins +```javascript +// Frontend listens for real-time updates +eventSource.addEventListener('status_update', function(e) { + const data = JSON.parse(e.data); + updateFileStatus(data); // Instant UI update +}); ``` -To enable plugins use: - -```bash -markitdown --use-plugins path-to-file.pdf -``` - -To find available plugins, search GitHub for the hashtag `#markitdown-plugin`. To develop a plugin, see `packages/markitdown-sample-plugin`. - -### Azure Document Intelligence - -To use Microsoft Document Intelligence for conversion: - -```bash -markitdown path-to-file.pdf -o document.md -d -e "" -``` - -More information about how to set up an Azure Document Intelligence Resource can be found [here](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/how-to-guides/create-document-intelligence-resource?view=doc-intel-4.0.0) - -### Python API - -Basic usage in Python: - ```python -from markitdown import MarkItDown - -md = MarkItDown(enable_plugins=False) # Set to True to enable plugins -result = md.convert("test.xlsx") -print(result.text_content) +# Backend sends immediate notifications +def notify_status_change(file_id, status): + broadcast_notification('status_update', { + 'file_id': file_id, + 'status': status + }) ``` -Document Intelligence conversion in Python: - -```python -from markitdown import MarkItDown - -md = MarkItDown(docintel_endpoint="") -result = md.convert("test.pdf") -print(result.text_content) -``` +## 🎯 Benefits Over Polling -To use Large Language Models for image descriptions (currently only for pptx and image files), provide `llm_client` and `llm_model`: +| Aspect | Polling | SSE (This Implementation) | +|--------|---------|---------------------------| +| **Latency** | 200-500ms delay | Instant (0ms) | +| **Server Load** | High (constant requests) | Low (push-only) | +| **Bandwidth** | Wasteful | Efficient | +| **User Experience** | Delayed updates | Real-time feedback | -```python -from markitdown import MarkItDown -from openai import OpenAI +## 📁 File Structure -client = OpenAI() -md = MarkItDown(llm_client=client, llm_model="gpt-4o", llm_prompt="optional custom prompt") -result = md.convert("example.jpg") -print(result.text_content) ``` - -### Docker - -```sh -docker build -t markitdown:latest . -docker run --rm -i markitdown:latest < ~/your-file.pdf > output.md +├── server.py # Flask backend with SSE +├── code.html # Web interface +├── requirements-server.txt # Python dependencies +├── start_converter.bat # Windows startup script +├── uploads/ # Temporary file storage +└── outputs/ # Converted markdown files ``` -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. - -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -### How to Contribute - -You can help by looking at issues or helping review PRs. Any issue or PR is welcome, but we have also marked some as 'open for contribution' and 'open for reviewing' to help facilitate community contributions. These are of course just suggestions and you are welcome to contribute in any way you like. - -
- -| | All | Especially Needs Help from Community | -| ---------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | -| **Issues** | [All Issues](https://github.com/microsoft/markitdown/issues) | [Issues open for contribution](https://github.com/microsoft/markitdown/issues?q=is%3Aissue+is%3Aopen+label%3A%22open+for+contribution%22) | -| **PRs** | [All PRs](https://github.com/microsoft/markitdown/pulls) | [PRs open for reviewing](https://github.com/microsoft/markitdown/pulls?q=is%3Apr+is%3Aopen+label%3A%22open+for+reviewing%22) | - -
- -### Running Tests and Checks +## 🔧 API Endpoints -- Navigate to the MarkItDown package: +- `GET /` - Serve web interface +- `POST /api/upload` - Upload files +- `POST /api/convert` - Start batch conversion +- `GET /api/status` - Get current job status +- `GET /api/events` - SSE endpoint for real-time updates +- `GET /api/download/` - Download converted file +- `DELETE /api/delete/` - Remove file from queue - ```sh - cd packages/markitdown - ``` +## 🎨 UI Components -- Install `hatch` in your environment and run tests: +- **File Drop Zone**: Visual drag-and-drop area +- **Conversion Queue**: Real-time file status list +- **Progress Bar**: Overall conversion progress +- **Toast Notifications**: Success/error messages +- **Live Indicator**: Connection status display - ```sh - pip install hatch # Other ways of installing hatch: https://hatch.pypa.io/dev/install/ - hatch shell - hatch test - ``` +## 🔒 Security Features - (Alternative) Use the Devcontainer which has all the dependencies installed: +- **Secure filename handling** with werkzeug +- **File size limits** (100MB max) +- **Input validation** on all endpoints +- **Error handling** with proper HTTP status codes - ```sh - # Reopen the project in Devcontainer and run: - hatch test - ``` +## 🚀 Performance Optimizations -- Run pre-commit checks before submitting a PR: `pre-commit run --all-files` +- **Background threading** for non-blocking conversions +- **SSE connection pooling** for multiple clients +- **Efficient file handling** with streaming +- **Auto-cleanup** of temporary files -### Contributing 3rd-party Plugins +## 🤝 Contributing -You can also contribute by creating and sharing 3rd party plugins. See `packages/markitdown-sample-plugin` for more details. +This batch converter extends MarkItDown's capabilities while maintaining compatibility with the core library. It adds a user-friendly web interface without modifying the original conversion logic. -## Trademarks +## 📄 License -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft -trademarks or logos is subject to and must follow -[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). -Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. -Any use of third-party trademarks or logos are subject to those third-party's policies. +Follows the same license as the main MarkItDown project. diff --git a/SECURITY.md b/SECURITY.md index b3c89efc..6b906d43 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,41 +1,41 @@ - - -## Security - -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). - -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). - - + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). + + diff --git a/SUPPORT.md b/SUPPORT.md deleted file mode 100644 index 291d4d43..00000000 --- a/SUPPORT.md +++ /dev/null @@ -1,25 +0,0 @@ -# TODO: The maintainer of this repo has not yet edited this file - -**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? - -- **No CSS support:** Fill out this template with information about how to file issues and get help. -- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. -- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. - -*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* - -# Support - -## How to file issues and get help - -This project uses GitHub Issues to track bugs and feature requests. Please search the existing -issues before filing new issues to avoid duplicates. For new issues, file your bug or -feature request as a new Issue. - -For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE -FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER -CHANNEL. WHERE WILL YOU HELP PEOPLE?**. - -## Microsoft Support Policy - -Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/code.html b/code.html new file mode 100644 index 00000000..76f20445 --- /dev/null +++ b/code.html @@ -0,0 +1,553 @@ + + + + + +MarkItDown Batch Converter + + + + + + + + +
+
+ +
+
+
+
+

MarkItDown

+

Batch Converter

+
+
+ +
+upload_file +

Drag and drop files here to add them to the queue.

+
+
+ +
+
+

Conversion Queue

+ +
+
+
+ +
+inbox +

Your queue is empty

+

Add files to get started.

+
+
+
+ + +
+
+
+ + + + \ No newline at end of file diff --git a/requirements-server.txt b/requirements-server.txt new file mode 100644 index 00000000..506fe92c --- /dev/null +++ b/requirements-server.txt @@ -0,0 +1,4 @@ +flask>=3.0.0 +flask-cors>=4.0.0 +werkzeug>=3.0.0 +markitdown[all]>=0.1.0 diff --git a/screen.png b/screen.png new file mode 100644 index 00000000..e3583443 Binary files /dev/null and b/screen.png differ diff --git a/server.py b/server.py new file mode 100644 index 00000000..df41a8e1 --- /dev/null +++ b/server.py @@ -0,0 +1,367 @@ +""" +Flask backend API for MarkItDown Batch Converter +Handles file uploads, conversions, and serves the frontend +""" +import os +import sys +import uuid +import json +from pathlib import Path +from flask import Flask, request, jsonify, send_from_directory, Response +from flask_cors import CORS +from werkzeug.utils import secure_filename +import threading +import time +import queue + +# Add the markitdown package to the path +sys.path.insert(0, str(Path(__file__).parent / "packages" / "markitdown" / "src")) + +from markitdown import MarkItDown + +app = Flask(__name__) +CORS(app) + +# Configuration +UPLOAD_FOLDER = Path('uploads') +OUTPUT_FOLDER = Path('outputs') +UPLOAD_FOLDER.mkdir(exist_ok=True) +OUTPUT_FOLDER.mkdir(exist_ok=True) + +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER +app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max file size + +# Store conversion jobs in memory (in production, use a database) +conversion_jobs = {} +job_lock = threading.Lock() + +# SSE notification system +notification_queues = {} +notification_lock = threading.Lock() + +# Initialize MarkItDown +markitdown = MarkItDown() + + +class ConversionJob: + def __init__(self, file_id, filename, filesize): + self.file_id = file_id + self.filename = filename + self.filesize = filesize + self.status = 'waiting' # waiting, converting, completed, failed + self.progress = 0 + self.error = None + self.output_file = None + self.created_at = time.time() + + +def broadcast_notification(event_type, data): + """Send notification to all connected SSE clients""" + with notification_lock: + message = f"event: {event_type}\ndata: {json.dumps(data)}\n\n" + # Send to all connected clients + for client_id, client_queue in list(notification_queues.items()): + try: + client_queue.put_nowait(message) + except queue.Full: + # Remove disconnected clients + del notification_queues[client_id] + + +def notify_status_change(file_id, status, **kwargs): + """Notify clients about status changes""" + with job_lock: + if file_id in conversion_jobs: + job = conversion_jobs[file_id] + data = { + 'file_id': file_id, + 'filename': job.filename, + 'status': status, + 'progress': job.progress, + 'error': job.error, + 'output_file': job.output_file, + **kwargs + } + broadcast_notification('status_update', data) + + +def convert_file_async(file_id, input_path, output_path): + """Convert file in background thread""" + try: + with job_lock: + conversion_jobs[file_id].status = 'converting' + conversion_jobs[file_id].progress = 10 + + # Notify conversion started + notify_status_change(file_id, 'converting', progress=10) + + # Perform conversion + result = markitdown.convert(input_path) + + with job_lock: + conversion_jobs[file_id].progress = 80 + + # Notify progress + notify_status_change(file_id, 'converting', progress=80) + + # Write output + with open(output_path, 'w', encoding='utf-8') as f: + f.write(result.text_content) + + with job_lock: + conversion_jobs[file_id].status = 'completed' + conversion_jobs[file_id].progress = 100 + conversion_jobs[file_id].output_file = output_path.name + + # Notify completion immediately + notify_status_change(file_id, 'completed', progress=100) + + except Exception as e: + with job_lock: + conversion_jobs[file_id].status = 'failed' + conversion_jobs[file_id].error = str(e) + + # Notify failure immediately + notify_status_change(file_id, 'failed', error=str(e)) + + +@app.route('/') +def index(): + """Serve the frontend HTML""" + return send_from_directory('.', 'code.html') + + +@app.route('/api/events') +def events(): + """Server-Sent Events endpoint for real-time notifications""" + def event_stream(): + # Create a unique client ID and queue + client_id = str(uuid.uuid4()) + client_queue = queue.Queue(maxsize=100) + + with notification_lock: + notification_queues[client_id] = client_queue + + try: + # Send initial connection confirmation + yield f"event: connected\ndata: {json.dumps({'client_id': client_id})}\n\n" + + # Send current status of all jobs + with job_lock: + for job in conversion_jobs.values(): + data = { + 'file_id': job.file_id, + 'filename': job.filename, + 'status': job.status, + 'progress': job.progress, + 'error': job.error, + 'output_file': job.output_file + } + yield f"event: status_update\ndata: {json.dumps(data)}\n\n" + + # Keep connection alive and send notifications + while True: + try: + # Wait for notification with timeout + message = client_queue.get(timeout=30) + yield message + except queue.Empty: + # Send heartbeat to keep connection alive + yield f"event: heartbeat\ndata: {json.dumps({'timestamp': time.time()})}\n\n" + + except GeneratorExit: + # Client disconnected + pass + finally: + # Clean up client queue + with notification_lock: + if client_id in notification_queues: + del notification_queues[client_id] + + return Response(event_stream(), mimetype='text/event-stream', + headers={'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Access-Control-Allow-Origin': '*'}) + + +@app.route('/api/upload', methods=['POST']) +def upload_files(): + """Handle file uploads""" + if 'files' not in request.files: + return jsonify({'error': 'No files provided'}), 400 + + files = request.files.getlist('files') + uploaded_files = [] + + for file in files: + if file.filename == '': + continue + + # Generate unique ID + file_id = str(uuid.uuid4()) + + # Secure filename + original_filename = secure_filename(file.filename) + filename = f"{file_id}_{original_filename}" + filepath = app.config['UPLOAD_FOLDER'] / filename + + # Save file + file.save(filepath) + + # Get file size + filesize = filepath.stat().st_size + + # Create job entry + with job_lock: + conversion_jobs[file_id] = ConversionJob( + file_id=file_id, + filename=original_filename, + filesize=filesize + ) + + uploaded_files.append({ + 'file_id': file_id, + 'filename': original_filename, + 'filesize': filesize, + 'status': 'waiting' + }) + + return jsonify({'files': uploaded_files}) + + +@app.route('/api/convert', methods=['POST']) +def convert_files(): + """Start conversion for all waiting files""" + data = request.json + file_ids = data.get('file_ids', []) + + if not file_ids: + # Convert all waiting files + with job_lock: + file_ids = [ + job.file_id for job in conversion_jobs.values() + if job.status == 'waiting' + ] + + # Start conversion threads + for file_id in file_ids: + with job_lock: + if file_id not in conversion_jobs: + continue + + job = conversion_jobs[file_id] + if job.status != 'waiting': + continue + + # Find input file + input_files = list(app.config['UPLOAD_FOLDER'].glob(f"{file_id}_*")) + if not input_files: + continue + + input_path = input_files[0] + output_path = app.config['OUTPUT_FOLDER'] / f"{file_id}_{Path(job.filename).stem}.md" + + # Start conversion in background thread + thread = threading.Thread( + target=convert_file_async, + args=(file_id, input_path, output_path) + ) + thread.daemon = True + thread.start() + + return jsonify({'message': f'Started conversion for {len(file_ids)} files'}) + + +@app.route('/api/status', methods=['GET']) +def get_status(): + """Get status of all conversion jobs""" + with job_lock: + jobs_list = [] + for job in conversion_jobs.values(): + jobs_list.append({ + 'file_id': job.file_id, + 'filename': job.filename, + 'filesize': job.filesize, + 'status': job.status, + 'progress': job.progress, + 'error': job.error, + 'output_file': job.output_file + }) + + return jsonify({'jobs': jobs_list}) + + +@app.route('/api/delete/', methods=['DELETE']) +def delete_file(file_id): + """Delete a file from the queue""" + with job_lock: + if file_id not in conversion_jobs: + return jsonify({'error': 'File not found'}), 404 + + job = conversion_jobs[file_id] + + # Delete input file + input_files = list(app.config['UPLOAD_FOLDER'].glob(f"{file_id}_*")) + for f in input_files: + f.unlink(missing_ok=True) + + # Delete output file if exists + if job.output_file: + output_path = app.config['OUTPUT_FOLDER'] / job.output_file + output_path.unlink(missing_ok=True) + + # Remove from jobs + del conversion_jobs[file_id] + + return jsonify({'message': 'File deleted successfully'}) + + +@app.route('/api/download/', methods=['GET']) +def download_file(file_id): + """Download converted markdown file""" + with job_lock: + if file_id not in conversion_jobs: + return jsonify({'error': 'File not found'}), 404 + + job = conversion_jobs[file_id] + if job.status != 'completed' or not job.output_file: + return jsonify({'error': 'Conversion not completed'}), 400 + + return send_from_directory( + app.config['OUTPUT_FOLDER'], + job.output_file, + as_attachment=True + ) + + +@app.route('/api/clear', methods=['POST']) +def clear_completed(): + """Clear all completed conversions""" + with job_lock: + completed_ids = [ + job.file_id for job in conversion_jobs.values() + if job.status == 'completed' + ] + + for file_id in completed_ids: + job = conversion_jobs[file_id] + + # Delete files + input_files = list(app.config['UPLOAD_FOLDER'].glob(f"{file_id}_*")) + for f in input_files: + f.unlink(missing_ok=True) + + if job.output_file: + output_path = app.config['OUTPUT_FOLDER'] / job.output_file + output_path.unlink(missing_ok=True) + + del conversion_jobs[file_id] + + return jsonify({'message': f'Cleared {len(completed_ids)} completed jobs'}) + + +if __name__ == '__main__': + print("Starting MarkItDown Batch Converter Server...") + print("Open http://localhost:5000 in your browser") + app.run(debug=True, port=5000, threaded=True) diff --git a/start_converter.bat b/start_converter.bat new file mode 100644 index 00000000..db625921 --- /dev/null +++ b/start_converter.bat @@ -0,0 +1,40 @@ +@echo off +echo ======================================== +echo MarkItDown Batch Converter +echo ======================================== +echo. + +REM Check if virtual environment exists +if not exist ".venv\Scripts\activate.bat" ( + echo Creating virtual environment... + python -m venv .venv + echo. +) + +REM Activate virtual environment +echo Activating virtual environment... +call .venv\Scripts\activate.bat +echo. + +REM Check if requirements are installed +pip show flask >nul 2>&1 +if errorlevel 1 ( + echo Installing dependencies... + pip install -r requirements-server.txt + echo. +) + +REM Start the server +echo Starting MarkItDown Batch Converter Server... +echo. +echo ======================================== +echo Server will be available at: +echo http://localhost:5000 +echo ======================================== +echo. +echo Press Ctrl+C to stop the server +echo. + +python server.py + +pause