From 4678c8a2a4c5f2984b2a8b3051b0376cf0c2bec4 Mon Sep 17 00:00:00 2001 From: AbSadiki Date: Fri, 3 Jan 2025 16:29:26 -0500 Subject: [PATCH 01/75] fix(transcription): IS_AUDIO_TRANSCRIPTION_CAPABLE should be iniztialized (#194) --- src/markitdown/_markitdown.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index 789c1e5..6df13e3 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -33,6 +33,7 @@ from bs4 import BeautifulSoup from charset_normalizer import from_path # Optional Transcription support +IS_AUDIO_TRANSCRIPTION_CAPABLE = False try: # Using warnings' catch_warnings to catch # pydub's warning of ffmpeg or avconv missing From d248621ba4e7f4f91dba22c000a17c62b394d0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Murat=20Can=20Kurtulu=C5=9F?= Date: Sat, 4 Jan 2025 00:34:39 +0300 Subject: [PATCH 02/75] feat: outlook ".msg" file converter (#196) * feat: outlook .msg converter * add test, adjust docstring --- pyproject.toml | 1 + src/markitdown/_markitdown.py | 75 ++++++++++++++++++++++++++ tests/test_files/test_outlook_msg.msg | Bin 0 -> 13312 bytes tests/test_markitdown.py | 13 +++++ 4 files changed, 89 insertions(+) create mode 100644 tests/test_files/test_outlook_msg.msg diff --git a/pyproject.toml b/pyproject.toml index 3e14cec..67f6825 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "pdfminer.six", "puremagic", "pydub", + "olefile", "youtube-transcript-api", "SpeechRecognition", "pathvalidate", diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index 6df13e3..d209b5e 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -21,6 +21,7 @@ from warnings import warn, resetwarnings, catch_warnings import mammoth import markdownify +import olefile import pandas as pd import pdfminer import pdfminer.high_level @@ -1077,6 +1078,79 @@ class ImageConverter(MediaConverter): return response.choices[0].message.content +class OutlookMsgConverter(DocumentConverter): + """Converts Outlook .msg files to markdown by extracting email metadata and content. + + Uses the olefile package to parse the .msg file structure and extract: + - Email headers (From, To, Subject) + - Email body content + """ + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not a MSG file + extension = kwargs.get("file_extension", "") + if extension.lower() != ".msg": + return None + + try: + msg = olefile.OleFileIO(local_path) + # Extract email metadata + md_content = "# Email Message\n\n" + + # Get headers + headers = { + "From": self._get_stream_data(msg, "__substg1.0_0C1F001F"), + "To": self._get_stream_data(msg, "__substg1.0_0E04001F"), + "Subject": self._get_stream_data(msg, "__substg1.0_0037001F"), + } + + # Add headers to markdown + for key, value in headers.items(): + if value: + md_content += f"**{key}:** {value}\n" + + md_content += "\n## Content\n\n" + + # Get email body + body = self._get_stream_data(msg, "__substg1.0_1000001F") + if body: + md_content += body + + msg.close() + + return DocumentConverterResult( + title=headers.get("Subject"), text_content=md_content.strip() + ) + + except Exception as e: + raise FileConversionException( + f"Could not convert MSG file '{local_path}': {str(e)}" + ) + + def _get_stream_data( + self, msg: olefile.OleFileIO, stream_path: str + ) -> Union[str, None]: + """Helper to safely extract and decode stream data from the MSG file.""" + try: + if msg.exists(stream_path): + data = msg.openstream(stream_path).read() + # Try UTF-16 first (common for .msg files) + try: + return data.decode("utf-16-le").strip() + except UnicodeDecodeError: + # Fall back to UTF-8 + try: + return data.decode("utf-8").strip() + except UnicodeDecodeError: + # Last resort - ignore errors + return data.decode("utf-8", errors="ignore").strip() + except Exception: + pass + return None + + class ZipConverter(DocumentConverter): """Converts ZIP files to markdown by extracting and converting all contained files. @@ -1286,6 +1360,7 @@ class MarkItDown: self.register_page_converter(IpynbConverter()) self.register_page_converter(PdfConverter()) self.register_page_converter(ZipConverter()) + self.register_page_converter(OutlookMsgConverter()) def convert( self, source: Union[str, requests.Response, Path], **kwargs: Any diff --git a/tests/test_files/test_outlook_msg.msg b/tests/test_files/test_outlook_msg.msg new file mode 100644 index 0000000000000000000000000000000000000000..05b087b77c785c8b57a479485b9715fb7dfecbb4 GIT binary patch literal 13312 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 JMf%!W;Qy?8Su_9u literal 0 HcmV?d00001 diff --git a/tests/test_markitdown.py b/tests/test_markitdown.py index 4a981bd..a0626d1 100644 --- a/tests/test_markitdown.py +++ b/tests/test_markitdown.py @@ -63,6 +63,15 @@ DOCX_TEST_STRINGS = [ "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", ] +MSG_TEST_STRINGS = [ + "# Email Message", + "**From:** test.sender@example.com", + "**To:** test.recipient@example.com", + "**Subject:** Test Email Message", + "## Content", + "This is the body of the test email message", +] + DOCX_COMMENT_TEST_STRINGS = [ "314b0a30-5b04-470b-b9f7-eed2c2bec74a", "49e168b7-d2ae-407f-a055-2167576f39a1", @@ -232,6 +241,10 @@ def test_markitdown_local() -> None: result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_mskanji.csv")) validate_strings(result, CSV_CP932_TEST_STRINGS) + # Test MSG (Outlook email) processing + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg")) + validate_strings(result, MSG_TEST_STRINGS) + @pytest.mark.skipif( skip_exiftool, From 08ed32869eae01d0b7c39944a092b90221f81ae6 Mon Sep 17 00:00:00 2001 From: yeungadrian <47532646+yeungadrian@users.noreply.github.com> Date: Fri, 3 Jan 2025 21:58:17 +0000 Subject: [PATCH 03/75] Feature/ Add xls support (#169) * add xlrd * add xls converter with tests --- pyproject.toml | 1 + src/markitdown/_markitdown.py | 27 ++++++++++++++++++++++++++- tests/test_files/test.xls | Bin 0 -> 27648 bytes tests/test_markitdown.py | 12 ++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/test_files/test.xls diff --git a/pyproject.toml b/pyproject.toml index 67f6825..9c113ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "python-pptx", "pandas", "openpyxl", + "xlrd", "pdfminer.six", "puremagic", "pydub", diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index d209b5e..50c83b4 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -726,7 +726,31 @@ class XlsxConverter(HtmlConverter): if extension.lower() != ".xlsx": return None - sheets = pd.read_excel(local_path, sheet_name=None) + sheets = pd.read_excel(local_path, sheet_name=None, engine="openpyxl") + md_content = "" + for s in sheets: + md_content += f"## {s}\n" + html_content = sheets[s].to_html(index=False) + md_content += self._convert(html_content).text_content.strip() + "\n\n" + + return DocumentConverterResult( + title=None, + text_content=md_content.strip(), + ) + + +class XlsConverter(HtmlConverter): + """ + Converts XLS files to Markdown, with each sheet presented as a separate Markdown table. + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a XLS + extension = kwargs.get("file_extension", "") + if extension.lower() != ".xls": + return None + + sheets = pd.read_excel(local_path, sheet_name=None, engine="xlrd") md_content = "" for s in sheets: md_content += f"## {s}\n" @@ -1353,6 +1377,7 @@ class MarkItDown: self.register_page_converter(BingSerpConverter()) self.register_page_converter(DocxConverter()) self.register_page_converter(XlsxConverter()) + self.register_page_converter(XlsConverter()) self.register_page_converter(PptxConverter()) self.register_page_converter(WavConverter()) self.register_page_converter(Mp3Converter()) diff --git a/tests/test_files/test.xls b/tests/test_files/test.xls new file mode 100644 index 0000000000000000000000000000000000000000..de4f368c24d489ff7aca786acc29d80652189eb9 GIT binary patch literal 27648 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 W3Gf|YY=Nff None: result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.xlsx")) validate_strings(result, XLSX_TEST_STRINGS) + # Test XLS processing + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.xls")) + for test_string in XLS_TEST_STRINGS: + text_content = result.text_content.replace("\\", "") + assert test_string in text_content + # Test DOCX processing result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.docx")) validate_strings(result, DOCX_TEST_STRINGS) From 731b39e7f5d36469b2912ed1608fd86c04a1ddcc Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 3 Jan 2025 14:34:33 -0800 Subject: [PATCH 04/75] Added a test for leading spaces. (#258) --- tests/test_markitdown.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_markitdown.py b/tests/test_markitdown.py index 1ac9041..9dc7374 100644 --- a/tests/test_markitdown.py +++ b/tests/test_markitdown.py @@ -257,6 +257,11 @@ def test_markitdown_local() -> None: result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg")) validate_strings(result, MSG_TEST_STRINGS) + # Test input with leading blank characters + input_data = b" \n\n\n

Test

" + result = markitdown.convert_stream(io.BytesIO(input_data), file_extension=".html") + assert "# Test" in result.text_content + @pytest.mark.skipif( skip_exiftool, From 436407288f01b5a2c31111062b0c2ac959dad443 Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 3 Jan 2025 16:03:11 -0800 Subject: [PATCH 05/75] If puremagic has no guesses, try again after ltrim. (#260) --- src/markitdown/_markitdown.py | 19 +++++++++++++++++++ tests/test_markitdown.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index 50c83b4..aceaa86 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -1594,6 +1594,25 @@ class MarkItDown: # Use puremagic to guess try: guesses = puremagic.magic_file(path) + + # Fix for: https://github.com/microsoft/markitdown/issues/222 + # If there are no guesses, then try again after trimming leading ASCII whitespaces. + # ASCII whitespace characters are those byte values in the sequence b' \t\n\r\x0b\f' + # (space, tab, newline, carriage return, vertical tab, form feed). + if len(guesses) == 0: + with open(path, "rb") as file: + while True: + char = file.read(1) + if not char: # End of file + break + if not char.isspace(): + file.seek(file.tell() - 1) + break + try: + guesses = puremagic.magic_stream(file) + except puremagic.main.PureError: + pass + extensions = list() for g in guesses: ext = g.extension.strip() diff --git a/tests/test_markitdown.py b/tests/test_markitdown.py index 9dc7374..e2d2e75 100644 --- a/tests/test_markitdown.py +++ b/tests/test_markitdown.py @@ -259,7 +259,7 @@ def test_markitdown_local() -> None: # Test input with leading blank characters input_data = b" \n\n\n

Test

" - result = markitdown.convert_stream(io.BytesIO(input_data), file_extension=".html") + result = markitdown.convert_stream(io.BytesIO(input_data)) assert "# Test" in result.text_content From 05b78e7ce18cf2f8d8d75058a1f2c98f9930318b Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 3 Jan 2025 16:40:43 -0800 Subject: [PATCH 06/75] Recognize json as plain text (if no other handlers are present). (#261) * Recognize json as plain text (if no other handlers are present). --- src/markitdown/_markitdown.py | 5 ++++- tests/test_files/test.json | 10 ++++++++++ tests/test_markitdown.py | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/test_files/test.json diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index aceaa86..b6acfe8 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -173,7 +173,10 @@ class PlainTextConverter(DocumentConverter): # Only accept text files if content_type is None: return None - elif "text/" not in content_type.lower(): + elif all( + not content_type.lower().startswith(type_prefix) + for type_prefix in ["text/", "application/json"] + ): return None text_content = str(from_path(local_path).best()) diff --git a/tests/test_files/test.json b/tests/test_files/test.json new file mode 100644 index 0000000..eba3059 --- /dev/null +++ b/tests/test_files/test.json @@ -0,0 +1,10 @@ +{ + "key1": "string_value", + "key2": 1234, + "key3": [ + "list_value1", + "list_value2" + ], + "5b64c88c-b3c3-4510-bcb8-da0b200602d8": "uuid_key", + "uuid_value": "9700dc99-6685-40b4-9a3a-5e406dcb37f3" +} diff --git a/tests/test_markitdown.py b/tests/test_markitdown.py index e2d2e75..3333bcb 100644 --- a/tests/test_markitdown.py +++ b/tests/test_markitdown.py @@ -145,6 +145,11 @@ LLM_TEST_STRINGS = [ "5bda1dd6", ] +JSON_TEST_STRINGS = [ + "5b64c88c-b3c3-4510-bcb8-da0b200602d8", + "9700dc99-6685-40b4-9a3a-5e406dcb37f3", +] + # --- Helper Functions --- def validate_strings(result, expected_strings, exclude_strings=None): @@ -257,6 +262,10 @@ def test_markitdown_local() -> None: result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg")) validate_strings(result, MSG_TEST_STRINGS) + # Test JSON processing + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.json")) + validate_strings(result, JSON_TEST_STRINGS) + # Test input with leading blank characters input_data = b" \n\n\n

Test

" result = markitdown.convert_stream(io.BytesIO(input_data)) From 265aea2edf31bf1b022992e59f0ade1e54903aee Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 6 Jan 2025 09:06:21 -0800 Subject: [PATCH 07/75] Removed the holiday away message from README.md (#266) --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index d2314c3..6bc91e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -> [!IMPORTANT] -> (12/19/24) Hello! MarkItDown team members will be resting and recharging with family and friends over the holiday period. Activity/responses on the project may be delayed during the period of Dec 21-Jan 06. We will be excited to engage with you in the new year! - # MarkItDown [![PyPI](https://img.shields.io/pypi/v/markitdown.svg)](https://pypi.org/project/markitdown/) From f58a864951da6c720d3e10987371133c67db296a Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 6 Jan 2025 12:43:47 -0800 Subject: [PATCH 08/75] Set exiftool path explicitly. (#267) --- src/markitdown/_markitdown.py | 39 ++++++++++++++++++++++++++--------- tests/test_markitdown.py | 32 ++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index b6acfe8..33806e1 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -892,14 +892,25 @@ class MediaConverter(DocumentConverter): Abstract class for multi-modal media (e.g., images and audio) """ - def _get_metadata(self, local_path): - exiftool = shutil.which("exiftool") - if not exiftool: + def _get_metadata(self, local_path, exiftool_path=None): + if not exiftool_path: + which_exiftool = shutil.which("exiftool") + if which_exiftool: + warn( + f"""Implicit discovery of 'exiftool' is disabled. If you would like to continue to use exiftool in MarkItDown, please set the exiftool_path parameter in the MarkItDown consructor. E.g., + + md = MarkItDown(exiftool_path="{which_exiftool}") + +This warning will be removed in future releases. +""", + DeprecationWarning, + ) + return None else: try: result = subprocess.run( - [exiftool, "-json", local_path], capture_output=True, text=True + [exiftool_path, "-json", local_path], capture_output=True, text=True ).stdout return json.loads(result)[0] except Exception: @@ -920,7 +931,7 @@ class WavConverter(MediaConverter): md_content = "" # Add metadata - metadata = self._get_metadata(local_path) + metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) if metadata: for f in [ "Title", @@ -975,7 +986,7 @@ class Mp3Converter(WavConverter): md_content = "" # Add metadata - metadata = self._get_metadata(local_path) + metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) if metadata: for f in [ "Title", @@ -1036,7 +1047,7 @@ class ImageConverter(MediaConverter): md_content = "" # Add metadata - metadata = self._get_metadata(local_path) + metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) if metadata: for f in [ "ImageSize", @@ -1325,6 +1336,7 @@ class MarkItDown: llm_client: Optional[Any] = None, llm_model: Optional[str] = None, style_map: Optional[str] = None, + exiftool_path: Optional[str] = None, # Deprecated mlm_client: Optional[Any] = None, mlm_model: Optional[str] = None, @@ -1334,6 +1346,9 @@ class MarkItDown: else: self._requests_session = requests_session + if exiftool_path is None: + exiftool_path = os.environ.get("EXIFTOOL_PATH") + # Handle deprecation notices ############################# if mlm_client is not None: @@ -1366,6 +1381,7 @@ class MarkItDown: self._llm_client = llm_client self._llm_model = llm_model self._style_map = style_map + self._exiftool_path = exiftool_path self._page_converters: List[DocumentConverter] = [] @@ -1549,12 +1565,15 @@ class MarkItDown: if "llm_model" not in _kwargs and self._llm_model is not None: _kwargs["llm_model"] = self._llm_model - # Add the list of converters for nested processing - _kwargs["_parent_converters"] = self._page_converters - if "style_map" not in _kwargs and self._style_map is not None: _kwargs["style_map"] = self._style_map + if "exiftool_path" not in _kwargs and self._exiftool_path is not None: + _kwargs["exiftool_path"] = self._exiftool_path + + # Add the list of converters for nested processing + _kwargs["_parent_converters"] = self._page_converters + # If we hit an error log it and keep trying try: res = converter.convert(local_path, **_kwargs) diff --git a/tests/test_markitdown.py b/tests/test_markitdown.py index 3333bcb..689d6f3 100644 --- a/tests/test_markitdown.py +++ b/tests/test_markitdown.py @@ -277,9 +277,29 @@ def test_markitdown_local() -> None: reason="do not run if exiftool is not installed", ) def test_markitdown_exiftool() -> None: - markitdown = MarkItDown() + # Test the automatic discovery of exiftool throws a warning + # and is disabled + try: + with catch_warnings(record=True) as w: + markitdown = MarkItDown() + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.jpg")) + assert len(w) == 1 + assert w[0].category is DeprecationWarning + assert result.text_content.strip() == "" + finally: + resetwarnings() - # Test JPG metadata processing + # Test explicitly setting the location of exiftool + which_exiftool = shutil.which("exiftool") + markitdown = MarkItDown(exiftool_path=which_exiftool) + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.jpg")) + for key in JPG_TEST_EXIFTOOL: + target = f"{key}: {JPG_TEST_EXIFTOOL[key]}" + assert target in result.text_content + + # Test setting the exiftool path through an environment variable + os.environ["EXIFTOOL_PATH"] = which_exiftool + markitdown = MarkItDown() result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.jpg")) for key in JPG_TEST_EXIFTOOL: target = f"{key}: {JPG_TEST_EXIFTOOL[key]}" @@ -341,8 +361,8 @@ def test_markitdown_llm() -> None: if __name__ == "__main__": """Runs this file's tests from the command line.""" - test_markitdown_remote() - test_markitdown_local() + # test_markitdown_remote() + # test_markitdown_local() test_markitdown_exiftool() - test_markitdown_deprecation() - test_markitdown_llm() + # test_markitdown_deprecation() + # test_markitdown_llm() From bfde8574204d1111309fbbff0b4347a3bf518676 Mon Sep 17 00:00:00 2001 From: KennyZhang1 <90438893+KennyZhang1@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:09:32 -0500 Subject: [PATCH 09/75] Add support for conversion via Document Intelligence (#303) * added cli params for doc intel * added DocumentIntelligenceConverter class implementation * initialized doc intel client instance field * added isolated doc_intel main conversion function * temp fix for ContentFormat import bug * ran tests for docintel and offline for many filetypes * push doc intel converter to the top of the stack * formatting changes * modified project toml file --- pyproject.toml | 2 + src/markitdown/__main__.py | 35 +++++++++++--- src/markitdown/_markitdown.py | 88 +++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c113ad..2a4e203 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,8 @@ dependencies = [ "pathvalidate", "charset-normalizer", "openai", + "azure-ai-documentintelligence", + "azure-identity" ] [project.urls] diff --git a/src/markitdown/__main__.py b/src/markitdown/__main__.py index b6cf963..69e8f0e 100644 --- a/src/markitdown/__main__.py +++ b/src/markitdown/__main__.py @@ -4,8 +4,8 @@ import argparse import sys from textwrap import dedent -from .__about__ import __version__ -from ._markitdown import MarkItDown, DocumentConverterResult +from __about__ import __version__ +from _markitdown import MarkItDown, DocumentConverterResult def main(): @@ -57,16 +57,37 @@ def main(): "--output", help="Output file name. If not provided, output is written to stdout.", ) + parser.add_argument( + "-d", + "--use-docintel", + action="store_true", + help="Use Document Intelligence to extract text instead of offline conversion. Requires a valid Document Intelligence Endpoint.", + ) + parser.add_argument( + "-e", + "--endpoint", + type=str, + help="Document Intelligence Endpoint. Required if using Document Intelligence.", + ) args = parser.parse_args() - if args.filename is None: - markitdown = MarkItDown() - result = markitdown.convert_stream(sys.stdin.buffer) - _handle_output(args, result) + if args.use_docintel: + if args.endpoint is None: + raise ValueError( + "Document Intelligence Endpoint is required when using Document Intelligence." + ) + elif args.filename is None: + raise ValueError("Filename is required when using Document Intelligence.") + markitdown = MarkItDown(docintel_endpoint=args.endpoint) else: markitdown = MarkItDown() + + if args.filename is None: + result = markitdown.convert_stream(sys.stdin.buffer) + else: result = markitdown.convert(args.filename) - _handle_output(args, result) + + _handle_output(args, result) def _handle_output(args, result: DocumentConverterResult): diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index 33806e1..ae6a7b4 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -33,6 +33,19 @@ import requests from bs4 import BeautifulSoup from charset_normalizer import from_path +# Azure imports +from azure.ai.documentintelligence import DocumentIntelligenceClient +from azure.ai.documentintelligence.models import ( + AnalyzeDocumentRequest, + AnalyzeResult, + DocumentAnalysisFeature, +) +from azure.identity import DefaultAzureCredential + +# TODO: currently, there is a bug in the document intelligence SDK with importing the "ContentFormat" enum. +# This constant is a temporary fix until the bug is resolved. +CONTENT_FORMAT = "markdown" + # Optional Transcription support IS_AUDIO_TRANSCRIPTION_CAPABLE = False try: @@ -1318,6 +1331,74 @@ class ZipConverter(DocumentConverter): ) +class DocumentIntelligenceConverter(DocumentConverter): + """Specialized DocumentConverter that uses Document Intelligence to extract text from documents.""" + + def __init__( + self, + endpoint: str, + api_version: str = "2024-07-31-preview", + ): + self.endpoint = endpoint + self.api_version = api_version + self.doc_intel_client = DocumentIntelligenceClient( + endpoint=self.endpoint, + api_version=self.api_version, + credential=DefaultAzureCredential(), + ) + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if extension is not supported by Document Intelligence + extension = kwargs.get("file_extension", "") + docintel_extensions = [ + ".pdf", + ".docx", + ".xlsx", + ".pptx", + ".html", + ".jpeg", + ".jpg", + ".png", + ".bmp", + ".tiff", + ".heif", + ] + if extension.lower() not in docintel_extensions: + return None + + # Get the bytestring for the local path + with open(local_path, "rb") as f: + file_bytes = f.read() + + # Certain document analysis features are not availiable for filetypes (.xlsx, .pptx, .html) + if extension.lower() in [".xlsx", ".pptx", ".html"]: + analysis_features = [] + else: + analysis_features = [ + DocumentAnalysisFeature.FORMULAS, # enable formula extraction + DocumentAnalysisFeature.OCR_HIGH_RESOLUTION, # enable high resolution OCR + DocumentAnalysisFeature.STYLE_FONT, # enable font style extraction + ] + + # Extract the text using Azure Document Intelligence + poller = self.doc_intel_client.begin_analyze_document( + model_id="prebuilt-layout", + body=AnalyzeDocumentRequest(bytes_source=file_bytes), + features=analysis_features, + output_content_format=CONTENT_FORMAT, # TODO: replace with "ContentFormat.MARKDOWN" when the bug is fixed + ) + result: AnalyzeResult = poller.result() + + # remove comments from the markdown content generated by Doc Intelligence and append to markdown string + markdown_text = re.sub(r"", "", result.content, flags=re.DOTALL) + return DocumentConverterResult( + title=None, + text_content=markdown_text, + ) + + class FileConversionException(BaseException): pass @@ -1337,6 +1418,7 @@ class MarkItDown: llm_model: Optional[str] = None, style_map: Optional[str] = None, exiftool_path: Optional[str] = None, + docintel_endpoint: Optional[str] = None, # Deprecated mlm_client: Optional[Any] = None, mlm_model: Optional[str] = None, @@ -1406,6 +1488,12 @@ class MarkItDown: self.register_page_converter(ZipConverter()) self.register_page_converter(OutlookMsgConverter()) + # Register Document Intelligence converter at the top of the stack if endpoint is provided + if docintel_endpoint is not None: + self.register_page_converter( + DocumentIntelligenceConverter(endpoint=docintel_endpoint) + ) + def convert( self, source: Union[str, requests.Response, Path], **kwargs: Any ) -> DocumentConverterResult: # TODO: deal with kwargs From bf6a15e9b5eb89820bf82c04cbe934bf62fb8617 Mon Sep 17 00:00:00 2001 From: KennyZhang1 <90438893+KennyZhang1@users.noreply.github.com> Date: Sat, 1 Feb 2025 01:23:26 -0500 Subject: [PATCH 10/75] Kennyzhang/docintel docs (#312) * updated docs to include doc intelligence * include reference to doc intel setup docs --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 6bc91e6..76a4d3f 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,20 @@ Or use `-o` to specify the output file: markitdown path-to-file.pdf -o document.md ``` +To use Document Intelligence conversion: + +```bash +markitdown path-to-file.pdf -o document.md -d -e "" +``` + You can also pipe content: ```bash cat path-to-file.pdf | markitdown ``` +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: @@ -51,6 +59,16 @@ result = md.convert("test.xlsx") print(result.text_content) ``` +Document Intelligence conversion in Python: + +```python +from markitdown import MarkItDown + +md = MarkItDown(docintel_endpoint="") +result = md.convert("test.pdf") +print(result.text_content) +``` + To use Large Language Models for image descriptions, provide `llm_client` and `llm_model`: ```python From 7bea2672a05f5877acb8690b20222593dab13788 Mon Sep 17 00:00:00 2001 From: ZeyuTeng96 <96521059+ZeyuTeng96@users.noreply.github.com> Date: Sun, 9 Feb 2025 12:28:35 +0800 Subject: [PATCH 11/75] remove leading and trailing \n for HtmlConverter (#262) --- src/markitdown/_markitdown.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index ae6a7b4..6f40547 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -236,6 +236,9 @@ class HtmlConverter(DocumentConverter): assert isinstance(webpage_text, str) + # remove leading and trailing \n + webpage_text = webpage_text.strip() + return DocumentConverterResult( title=None if soup.title is None else soup.title.string, text_content=webpage_text, From 3090917a49dc8ec94682c47747f3e2692e3953ae Mon Sep 17 00:00:00 2001 From: James Hickey Date: Sun, 9 Feb 2025 00:30:13 -0400 Subject: [PATCH 12/75] Typo fixed (#270) --- src/markitdown/_markitdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index 6f40547..e4884ec 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -217,7 +217,7 @@ class HtmlConverter(DocumentConverter): return result def _convert(self, html_content: str) -> Union[None, DocumentConverterResult]: - """Helper function that converts and HTML string.""" + """Helper function that converts an HTML string.""" # Parse the string soup = BeautifulSoup(html_content, "html.parser") From 7cf5e0bb23980cd004ceeea476c1bde3246d3c84 Mon Sep 17 00:00:00 2001 From: masquare Date: Sun, 9 Feb 2025 05:37:34 +0100 Subject: [PATCH 13/75] feat(pptx): support image description with LLM for pptx files (#306) --- src/markitdown/_markitdown.py | 62 +++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index e4884ec..9f610f6 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -787,6 +787,35 @@ class PptxConverter(HtmlConverter): Converts PPTX files to Markdown. Supports heading, tables and images with alt text. """ + def _get_llm_description( + self, llm_client, llm_model, image_blob, content_type, prompt=None + ): + if prompt is None or prompt.strip() == "": + prompt = "Write a detailed alt text for this image with less than 50 words." + + image_base64 = base64.b64encode(image_blob).decode("utf-8") + data_uri = f"data:{content_type};base64,{image_base64}" + + messages = [ + { + "role": "user", + "content": [ + { + "type": "image_url", + "image_url": { + "url": data_uri, + }, + }, + {"type": "text", "text": prompt}, + ], + } + ] + + response = llm_client.chat.completions.create( + model=llm_model, messages=messages + ) + return response.choices[0].message.content + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a PPTX extension = kwargs.get("file_extension", "") @@ -807,17 +836,38 @@ class PptxConverter(HtmlConverter): # Pictures if self._is_picture(shape): # https://github.com/scanny/python-pptx/pull/512#issuecomment-1713100069 - alt_text = "" - try: - alt_text = shape._element._nvXxPr.cNvPr.attrib.get("descr", "") - except Exception: - pass + + llm_description = None + alt_text = None + + llm_client = kwargs.get("llm_client") + llm_model = kwargs.get("llm_model") + if llm_client is not None and llm_model is not None: + try: + llm_description = self._get_llm_description( + llm_client, + llm_model, + shape.image.blob, + shape.image.content_type, + ) + except Exception: + # Unable to describe with LLM + pass + + if not llm_description: + try: + alt_text = shape._element._nvXxPr.cNvPr.attrib.get( + "descr", "" + ) + except Exception: + # Unable to get alt text + pass # A placeholder name filename = re.sub(r"\W", "", shape.name) + ".jpg" md_content += ( "\n![" - + (alt_text if alt_text else shape.name) + + (llm_description or alt_text or shape.name) + "](" + filename + ")\n" From 2a4f7bb6a8e3a5a4bd73e0dcfe1849205f62708a Mon Sep 17 00:00:00 2001 From: Werner Robitza Date: Sun, 9 Feb 2025 05:50:38 +0100 Subject: [PATCH 14/75] fix: argparse CLI option ordering, fixes #268 (#290) * fix: argparse CLI option ordering, fixes #268 * Fixed formatting. --- src/markitdown/__main__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/markitdown/__main__.py b/src/markitdown/__main__.py index 69e8f0e..353be84 100644 --- a/src/markitdown/__main__.py +++ b/src/markitdown/__main__.py @@ -4,8 +4,8 @@ import argparse import sys from textwrap import dedent -from __about__ import __version__ -from _markitdown import MarkItDown, DocumentConverterResult +from .__about__ import __version__ +from ._markitdown import MarkItDown, DocumentConverterResult def main(): @@ -51,24 +51,27 @@ def main(): help="show the version number and exit", ) - parser.add_argument("filename", nargs="?") parser.add_argument( "-o", "--output", help="Output file name. If not provided, output is written to stdout.", ) + parser.add_argument( "-d", "--use-docintel", action="store_true", help="Use Document Intelligence to extract text instead of offline conversion. Requires a valid Document Intelligence Endpoint.", ) + parser.add_argument( "-e", "--endpoint", type=str, help="Document Intelligence Endpoint. Required if using Document Intelligence.", ) + + parser.add_argument("filename", nargs="?") args = parser.parse_args() if args.use_docintel: From 73ba69d8cd44d3a95bbeb8d99188087777963402 Mon Sep 17 00:00:00 2001 From: wunde005 Date: Sat, 8 Feb 2025 22:58:13 -0600 Subject: [PATCH 15/75] For csv files mimetypes.guess_type is returning "application/vnd.ms-excel" on windows causing an invalid mime type in plaintextconverter. In reference to issue: https://github.com/microsoft/markitdown/issues/150 (#273) --- src/markitdown/_markitdown.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py index 9f610f6..e68b099 100644 --- a/src/markitdown/_markitdown.py +++ b/src/markitdown/_markitdown.py @@ -46,6 +46,9 @@ from azure.identity import DefaultAzureCredential # This constant is a temporary fix until the bug is resolved. CONTENT_FORMAT = "markdown" +# Override mimetype for csv to fix issue on windows +mimetypes.add_type("text/csv", ".csv") + # Optional Transcription support IS_AUDIO_TRANSCRIPTION_CAPABLE = False try: From c73afcffeadfbc116ff0260df6f720a804bb9675 Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 10 Feb 2025 15:21:44 -0800 Subject: [PATCH 16/75] Cleanup and refactor, in preparation for plugin support. (#318) * Work started moving converters to individual files. * Significant cleanup and refactor. * Moved everything to a packages subfolder. * Added sample plugin. * Added instructions to the README.md * Bumped version, and added a note about compatibility. --- .github/workflows/tests.yml | 9 +- README.md | 91 +- packages/markitdown-sample-plugin/README.md | 96 + .../markitdown-sample-plugin/pyproject.toml | 70 + .../markitdown_sample_plugin}/__about__.py | 2 +- .../src/markitdown_sample_plugin/__init__.py | 13 + .../src/markitdown_sample_plugin/_plugin.py | 39 + .../src/markitdown_sample_plugin}/py.typed | 0 .../tests}/__init__.py | 0 .../tests/test_files/test.rtf | 251 +++ .../tests/test_sample_plugin.py | 40 + packages/markitdown/README.md | 52 + .../markitdown/pyproject.toml | 0 .../markitdown/src/markitdown/__about__.py | 4 + .../markitdown/src/markitdown/__init__.py | 22 + .../markitdown/src}/markitdown/__main__.py | 37 +- .../markitdown/src/markitdown/_exceptions.py | 37 + .../markitdown/src/markitdown/_markitdown.py | 440 ++++ .../src/markitdown/converters/__init__.py | 45 + .../src/markitdown/converters/_base.py | 34 + .../converters/_bing_serp_converter.py | 81 + .../converters/_doc_intel_converter.py | 85 + .../markitdown/converters/_docx_converter.py | 31 + .../markitdown/converters/_html_converter.py | 51 + .../markitdown/converters/_image_converter.py | 87 + .../markitdown/converters/_ipynb_converter.py | 70 + .../src/markitdown/converters/_markdownify.py | 87 + .../markitdown/converters/_media_converter.py | 36 + .../markitdown/converters/_mp3_converter.py | 84 + .../converters/_outlook_msg_converter.py | 76 + .../markitdown/converters/_pdf_converter.py | 21 + .../converters/_plain_text_converter.py | 33 + .../markitdown/converters/_pptx_converter.py | 180 ++ .../markitdown/converters/_rss_converter.py | 143 ++ .../markitdown/converters/_wav_converter.py | 67 + .../converters/_wikipedia_converter.py | 56 + .../markitdown/converters/_xlsx_converter.py | 54 + .../converters/_youtube_converter.py | 148 ++ .../markitdown/converters/_zip_converter.py | 135 ++ packages/markitdown/src/markitdown/py.typed | 0 packages/markitdown/tests/__init__.py | 3 + .../markitdown/tests}/test_files/test.docx | Bin .../markitdown/tests}/test_files/test.jpg | Bin .../markitdown/tests}/test_files/test.json | 0 .../markitdown/tests}/test_files/test.pptx | Bin .../markitdown/tests}/test_files/test.xls | Bin .../markitdown/tests}/test_files/test.xlsx | Bin .../tests}/test_files/test_blog.html | 0 .../tests}/test_files/test_files.zip | Bin .../markitdown/tests}/test_files/test_llm.jpg | Bin .../tests}/test_files/test_mskanji.csv | 0 .../tests}/test_files/test_notebook.ipynb | 0 .../tests}/test_files/test_outlook_msg.msg | Bin .../markitdown/tests}/test_files/test_rss.xml | 0 .../tests}/test_files/test_serp.html | 0 .../tests}/test_files/test_wikipedia.html | 0 .../tests}/test_files/test_with_comment.docx | Bin .../markitdown/tests}/test_markitdown.py | 34 - src/markitdown/__init__.py | 11 - src/markitdown/_markitdown.py | 1801 ----------------- 60 files changed, 2755 insertions(+), 1901 deletions(-) create mode 100644 packages/markitdown-sample-plugin/README.md create mode 100644 packages/markitdown-sample-plugin/pyproject.toml rename {src/markitdown => packages/markitdown-sample-plugin/src/markitdown_sample_plugin}/__about__.py (81%) create mode 100644 packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__init__.py create mode 100644 packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py rename {src/markitdown => packages/markitdown-sample-plugin/src/markitdown_sample_plugin}/py.typed (100%) rename {tests => packages/markitdown-sample-plugin/tests}/__init__.py (100%) create mode 100755 packages/markitdown-sample-plugin/tests/test_files/test.rtf create mode 100644 packages/markitdown-sample-plugin/tests/test_sample_plugin.py create mode 100644 packages/markitdown/README.md rename pyproject.toml => packages/markitdown/pyproject.toml (100%) create mode 100644 packages/markitdown/src/markitdown/__about__.py create mode 100644 packages/markitdown/src/markitdown/__init__.py rename {src => packages/markitdown/src}/markitdown/__main__.py (67%) create mode 100644 packages/markitdown/src/markitdown/_exceptions.py create mode 100644 packages/markitdown/src/markitdown/_markitdown.py create mode 100644 packages/markitdown/src/markitdown/converters/__init__.py create mode 100644 packages/markitdown/src/markitdown/converters/_base.py create mode 100644 packages/markitdown/src/markitdown/converters/_bing_serp_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_doc_intel_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_docx_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_html_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_image_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_ipynb_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_markdownify.py create mode 100644 packages/markitdown/src/markitdown/converters/_media_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_mp3_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_pdf_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_plain_text_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_pptx_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_rss_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_wav_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_wikipedia_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_xlsx_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_youtube_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_zip_converter.py create mode 100644 packages/markitdown/src/markitdown/py.typed create mode 100644 packages/markitdown/tests/__init__.py rename {tests => packages/markitdown/tests}/test_files/test.docx (100%) rename {tests => packages/markitdown/tests}/test_files/test.jpg (100%) rename {tests => packages/markitdown/tests}/test_files/test.json (100%) rename {tests => packages/markitdown/tests}/test_files/test.pptx (100%) rename {tests => packages/markitdown/tests}/test_files/test.xls (100%) rename {tests => packages/markitdown/tests}/test_files/test.xlsx (100%) rename {tests => packages/markitdown/tests}/test_files/test_blog.html (100%) rename {tests => packages/markitdown/tests}/test_files/test_files.zip (100%) rename {tests => packages/markitdown/tests}/test_files/test_llm.jpg (100%) rename {tests => packages/markitdown/tests}/test_files/test_mskanji.csv (100%) rename {tests => packages/markitdown/tests}/test_files/test_notebook.ipynb (100%) rename {tests => packages/markitdown/tests}/test_files/test_outlook_msg.msg (100%) rename {tests => packages/markitdown/tests}/test_files/test_rss.xml (100%) rename {tests => packages/markitdown/tests}/test_files/test_serp.html (100%) rename {tests => packages/markitdown/tests}/test_files/test_wikipedia.html (100%) rename {tests => packages/markitdown/tests}/test_files/test_with_comment.docx (100%) rename {tests => packages/markitdown/tests}/test_markitdown.py (92%) delete mode 100644 src/markitdown/__init__.py delete mode 100644 src/markitdown/_markitdown.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c4dbdcf..78c7cdc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,14 +12,7 @@ jobs: 3.10 3.11 3.12 - - name: Set up pip cache - if: runner.os == 'Linux' - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }} - restore-keys: ${{ runner.os }}-pip- - name: Install Hatch run: pipx install hatch - name: Run tests - run: hatch test + run: cd packages/markitdown; hatch test diff --git a/README.md b/README.md index 76a4d3f..8ac2fe3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ ![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) +> [!IMPORTANT] +> MarkItDown 0.0.2 alpha 1 (0.0.2a1) introduces a plugin-based architecture. As much as was possible, command-line and Python interfaces have remained the same as 0.0.1a3 to support backward compatibility. Please report any issues you encounter. Some interface changes may yet occur as we continue to refine MarkItDown to a first non-alpha release. MarkItDown is a utility for converting various files to Markdown (e.g., for indexing, text analysis, etc). It supports: @@ -16,8 +18,15 @@ It supports: - HTML - Text-based formats (CSV, JSON, XML) - ZIP files (iterates over contents) +- ... and more! -To install MarkItDown, use pip: `pip install markitdown`. Alternatively, you can install it from the source: `pip install -e .` +To install MarkItDown, use pip: `pip install markitdown`. Alternatively, you can install it from the source: + +```bash +git clone git@github.com:microsoft/markitdown.git +cd markitdown +pip install -e packages/markitdown +``` ## Usage @@ -33,20 +42,39 @@ Or use `-o` to specify the output file: markitdown path-to-file.pdf -o document.md ``` -To use Document Intelligence conversion: - -```bash -markitdown path-to-file.pdf -o document.md -d -e "" -``` - You can also pipe content: ```bash cat path-to-file.pdf | markitdown ``` +### Plugins + +MarkItDown also supports 3rd-party plugins. Plugins are disabled by default. To list installed plugins: + +```bash +markitdown --list-plugins +``` + +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: @@ -54,7 +82,7 @@ Basic usage in Python: ```python from markitdown import MarkItDown -md = MarkItDown() +md = MarkItDown(enable_plugins=False) # Set to True to enable plugins result = md.convert("test.xlsx") print(result.text_content) ``` @@ -87,42 +115,6 @@ print(result.text_content) docker build -t markitdown:latest . docker run --rm -i markitdown:latest < ~/your-file.pdf > output.md ``` -
- -Batch Processing Multiple Files - -This example shows how to convert multiple files to markdown format in a single run. The script processes all supported files in a directory and creates corresponding markdown files. - - -```python convert.py -from markitdown import MarkItDown -from openai import OpenAI -import os -client = OpenAI(api_key="your-api-key-here") -md = MarkItDown(llm_client=client, llm_model="gpt-4o-2024-11-20") -supported_extensions = ('.pptx', '.docx', '.pdf', '.jpg', '.jpeg', '.png') -files_to_convert = [f for f in os.listdir('.') if f.lower().endswith(supported_extensions)] -for file in files_to_convert: - print(f"\nConverting {file}...") - try: - md_file = os.path.splitext(file)[0] + '.md' - result = md.convert(file) - with open(md_file, 'w') as f: - f.write(result.text_content) - - print(f"Successfully converted {file} to {md_file}") - except Exception as e: - print(f"Error converting {file}: {str(e)}") - -print("\nAll conversions completed!") -``` -2. Place the script in the same directory as your files -3. Install required packages: like openai -4. Run script ```bash python convert.py ``` - -Note that original files will remain unchanged and new markdown files are created with the same base name. - -
## Contributing @@ -154,6 +146,12 @@ You can help by looking at issues or helping review PRs. Any issue or PR is welc ### Running Tests and Checks +- Navigate to the MarkItDown package: + + ```sh + cd packages/markitdown + ``` + - Install `hatch` in your environment and run tests: ```sh pip install hatch # Other ways of installing hatch: https://hatch.pypa.io/dev/install/ @@ -169,6 +167,11 @@ You can help by looking at issues or helping review PRs. Any issue or PR is welc - Run pre-commit checks before submitting a PR: `pre-commit run --all-files` +### Contributing 3rd-party Plugins + +You can also contribute by creating and sharing 3rd party plugins. See `packages/markitdown-sample-plugin` for more details. + + ## Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft diff --git a/packages/markitdown-sample-plugin/README.md b/packages/markitdown-sample-plugin/README.md new file mode 100644 index 0000000..9b15ca0 --- /dev/null +++ b/packages/markitdown-sample-plugin/README.md @@ -0,0 +1,96 @@ +# MarkItDown Sample Plugin + +[![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) + + +This project shows how to create a sample plugin for MarkItDown. The most important parts are as follows: + +FNext, implement your custom DocumentConverter: + +```python +from typing import Union +from markitdown import DocumentConverter, DocumentConverterResult + +class RtfConverter(DocumentConverter): + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not an RTF file + extension = kwargs.get("file_extension", "") + if extension.lower() != ".rtf": + return None + + # Implement the conversion logic here ... + + # Return the result + return DocumentConverterResult( + title=title, + text_content=text_content, + ) +``` + +Next, make sure your package implements and exports the following: + +```python +# The version of the plugin interface that this plugin uses. +# The only supported version is 1 for now. +__plugin_interface_version__ = 1 + +# The main entrypoint for the plugin. This is called each time MarkItDown instances are created. +def register_converters(markitdown: MarkItDown, **kwargs): + """ + Called during construction of MarkItDown instances to register converters provided by plugins. + """ + + # Simply create and attach an RtfConverter instance + markitdown.register_converter(RtfConverter()) +``` + + +Finally, create an entrypoint in the `pyproject.toml` file: + +```toml +[project.entry-points."markitdown.plugin"] +sample_plugin = "markitdown_sample_plugin" +``` + +Here, the value of `sample_plugin` can be any key, but should ideally be the name of the plugin. The value is the fully qualified name of the package implementing the plugin. + + +## Installation + +To use the plugin with MarkItDown, it must be installed. To install the plugin from the current directory use: + +```bash +pip install -e . +``` + +Once the plugin package is installed, verify that it is available to MarkItDown by running: + +```bash +markitdown --list-plugins +``` + +To use the plugin for a conversion use the `--use-plugins` flag. For example, to convert a PDF: + +```bash +markitdown --use-plugins path-to-file.pdf +``` + +In Python, plugins can be enabled as follows: + +```python +from markitdown import MarkItDown + +md = MarkItDown(enable_plugins=True) +result = md.convert("path-to-file.pdf") +print(result.text_content) +``` + +## Trademarks + +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. diff --git a/packages/markitdown-sample-plugin/pyproject.toml b/packages/markitdown-sample-plugin/pyproject.toml new file mode 100644 index 0000000..aaf2012 --- /dev/null +++ b/packages/markitdown-sample-plugin/pyproject.toml @@ -0,0 +1,70 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "markitdown-sample-plugin" +dynamic = ["version"] +description = 'A sample plugin for the "markitdown" library.' +readme = "README.md" +requires-python = ">=3.10" +license = "MIT" +keywords = [] +authors = [ + { name = "Adam Fourney", email = "adamfo@microsoft.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "markitdown", + "striprtf", +] + +[project.urls] +Documentation = "https://github.com/microsoft/markitdown#readme" +Issues = "https://github.com/microsoft/markitdown/issues" +Source = "https://github.com/microsoft/markitdown" + +[tool.hatch.version] +path = "src/markitdown_sample_plugin/__about__.py" + +# IMPORTANT: MarkItDown will look for this entry point to find the plugin. +[project.entry-points."markitdown.plugin"] +sample_plugin = "markitdown_sample_plugin" + +[tool.hatch.envs.types] +extra-dependencies = [ + "mypy>=1.0.0", +] +[tool.hatch.envs.types.scripts] +check = "mypy --install-types --non-interactive {args:src/markitdown_sample_plugin tests}" + +[tool.coverage.run] +source_pkgs = ["markitdown-sample-plugin", "tests"] +branch = true +parallel = true +omit = [ + "src/markitdown_sample_plugin/__about__.py", +] + +[tool.coverage.paths] +markitdown-sample-plugin = ["src/markitdown_sample_plugin", "*/markitdown-sample-plugin/src/markitdown_sample_plugin"] +tests = ["tests", "*/markitdown-sample-plugin/tests"] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] + +[tool.hatch.build.targets.sdist] +only-include = ["src/markitdown_sample_plugin"] diff --git a/src/markitdown/__about__.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py similarity index 81% rename from src/markitdown/__about__.py rename to packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py index a365900..fa67ccb 100644 --- a/src/markitdown/__about__.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.0.1a3" +__version__ = "0.0.1a2" diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__init__.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__init__.py new file mode 100644 index 0000000..8a64f99 --- /dev/null +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__init__.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT + +from ._plugin import __plugin_interface_version__, register_converters, RtfConverter +from .__about__ import __version__ + +__all__ = [ + "__version__", + "__plugin_interface_version__", + "register_converters", + "RtfConverter", +] diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py new file mode 100644 index 0000000..c8c4fef --- /dev/null +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py @@ -0,0 +1,39 @@ +from typing import Union +from striprtf.striprtf import rtf_to_text + +from markitdown import MarkItDown, DocumentConverter, DocumentConverterResult + +__plugin_interface_version__ = ( + 1 # The version of the plugin interface that this plugin uses +) + + +def register_converters(markitdown: MarkItDown, **kwargs): + """ + Called during construction of MarkItDown instances to register converters provided by plugins. + """ + + # Simply create and attach an RtfConverter instance + markitdown.register_converter(RtfConverter()) + + +class RtfConverter(DocumentConverter): + """ + Converts an RTF file to in the simplest possible way. + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a DOCX + extension = kwargs.get("file_extension", "") + if extension.lower() != ".rtf": + return None + + # Read the RTF file + with open(local_path, "r") as f: + rtf = f.read() + + # Return the result + return DocumentConverterResult( + title=None, + text_content=rtf_to_text(rtf), + ) diff --git a/src/markitdown/py.typed b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/py.typed similarity index 100% rename from src/markitdown/py.typed rename to packages/markitdown-sample-plugin/src/markitdown_sample_plugin/py.typed diff --git a/tests/__init__.py b/packages/markitdown-sample-plugin/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to packages/markitdown-sample-plugin/tests/__init__.py diff --git a/packages/markitdown-sample-plugin/tests/test_files/test.rtf b/packages/markitdown-sample-plugin/tests/test_files/test.rtf new file mode 100755 index 0000000..48629ef --- /dev/null +++ b/packages/markitdown-sample-plugin/tests/test_files/test.rtf @@ -0,0 +1,251 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;} +{\f42\fbidi \fswiss\fcharset0\fprq2 Aptos Display;}{\f43\fbidi \fswiss\fcharset0\fprq2 Aptos;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2 Aptos Display;}{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2 Aptos;} +{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f51\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f52\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\f54\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f55\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f56\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f57\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\f58\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f59\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f391\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}{\f392\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;} +{\f394\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f395\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f398\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}{\f399\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);} +{\f471\fbidi \fswiss\fcharset238\fprq2 Aptos Display CE;}{\f472\fbidi \fswiss\fcharset204\fprq2 Aptos Display Cyr;}{\f474\fbidi \fswiss\fcharset161\fprq2 Aptos Display Greek;}{\f475\fbidi \fswiss\fcharset162\fprq2 Aptos Display Tur;} +{\f478\fbidi \fswiss\fcharset186\fprq2 Aptos Display Baltic;}{\f479\fbidi \fswiss\fcharset163\fprq2 Aptos Display (Vietnamese);}{\f481\fbidi \fswiss\fcharset238\fprq2 Aptos CE;}{\f482\fbidi \fswiss\fcharset204\fprq2 Aptos Cyr;} +{\f484\fbidi \fswiss\fcharset161\fprq2 Aptos Greek;}{\f485\fbidi \fswiss\fcharset162\fprq2 Aptos Tur;}{\f488\fbidi \fswiss\fcharset186\fprq2 Aptos Baltic;}{\f489\fbidi \fswiss\fcharset163\fprq2 Aptos (Vietnamese);} +{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Aptos Display CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Aptos Display Cyr;} +{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Aptos Display Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Aptos Display Tur;}{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Aptos Display Baltic;} +{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Aptos Display (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Aptos CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Aptos Cyr;} +{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Aptos Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Aptos Tur;}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Aptos Baltic;} +{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Aptos (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; +\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\caccentone\ctint255\cshade191\red15\green71\blue97; +\ctextone\ctint166\cshade255\red89\green89\blue89;\ctextone\ctint216\cshade255\red39\green39\blue39;\ctextone\ctint191\cshade255\red64\green64\blue64;}{\*\defchp \f31506\fs24\kerning2 }{\*\defpap \ql \li0\ri0\sa160\sl278\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl278\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1025 +\ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\kerning2\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\s1\ql \li0\ri0\sb360\sa80\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel0\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs40\alang1025 \ltrch\fcs0 +\fs40\cf19\lang1033\langfe1033\kerning2\loch\f31502\hich\af31502\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink15 \sqformat \spriority9 \styrsid15678446 heading 1;}{\s2\ql \li0\ri0\sb160\sa80\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs32\alang1025 \ltrch\fcs0 +\fs32\cf19\lang1033\langfe1033\kerning2\loch\f31502\hich\af31502\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink16 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 2;}{\s3\ql \li0\ri0\sb160\sa80\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel2\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs28\alang1025 \ltrch\fcs0 +\fs28\cf19\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink17 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 3;}{\s4\ql \li0\ri0\sb80\sa40\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel3\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ai\af31503\afs24\alang1025 \ltrch\fcs0 +\i\fs24\cf19\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink18 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 4;}{\s5\ql \li0\ri0\sb80\sa40\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel4\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs24\alang1025 \ltrch\fcs0 +\fs24\cf19\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink19 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 5;}{\s6\ql \li0\ri0\sb40\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel5\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ai\af31503\afs24\alang1025 \ltrch\fcs0 +\i\fs24\cf20\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink20 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 6;}{\s7\ql \li0\ri0\sb40\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel6\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs24\alang1025 \ltrch\fcs0 +\fs24\cf20\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink21 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 7;}{\s8\ql \li0\ri0\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel7\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ai\af31503\afs24\alang1025 \ltrch\fcs0 +\i\fs24\cf21\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink22 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 8;}{\s9\ql \li0\ri0\sl278\slmult1 +\keep\keepn\widctlpar\wrapdefault\aspalpha\aspnum\faauto\outlinelevel8\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs24\alang1025 \ltrch\fcs0 +\fs24\cf21\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink23 \ssemihidden \sunhideused \sqformat \spriority9 \styrsid15678446 heading 9;}{\*\cs10 \additive +\ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl278\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\kerning2\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 +\additive \rtlch\fcs1 \af31503\afs40 \ltrch\fcs0 \fs40\cf19\loch\f31502\hich\af31502\dbch\af31501 \sbasedon10 \slink1 \spriority9 \styrsid15678446 Heading 1 Char;}{\*\cs16 \additive \rtlch\fcs1 \af31503\afs32 \ltrch\fcs0 +\fs32\cf19\loch\f31502\hich\af31502\dbch\af31501 \sbasedon10 \slink2 \ssemihidden \spriority9 \styrsid15678446 Heading 2 Char;}{\*\cs17 \additive \rtlch\fcs1 \af31503\afs28 \ltrch\fcs0 \fs28\cf19\dbch\af31501 +\sbasedon10 \slink3 \ssemihidden \spriority9 \styrsid15678446 Heading 3 Char;}{\*\cs18 \additive \rtlch\fcs1 \ai\af31503 \ltrch\fcs0 \i\cf19\dbch\af31501 \sbasedon10 \slink4 \ssemihidden \spriority9 \styrsid15678446 Heading 4 Char;}{\*\cs19 \additive +\rtlch\fcs1 \af31503 \ltrch\fcs0 \cf19\dbch\af31501 \sbasedon10 \slink5 \ssemihidden \spriority9 \styrsid15678446 Heading 5 Char;}{\*\cs20 \additive \rtlch\fcs1 \ai\af31503 \ltrch\fcs0 \i\cf20\dbch\af31501 +\sbasedon10 \slink6 \ssemihidden \spriority9 \styrsid15678446 Heading 6 Char;}{\*\cs21 \additive \rtlch\fcs1 \af31503 \ltrch\fcs0 \cf20\dbch\af31501 \sbasedon10 \slink7 \ssemihidden \spriority9 \styrsid15678446 Heading 7 Char;}{\*\cs22 \additive +\rtlch\fcs1 \ai\af31503 \ltrch\fcs0 \i\cf21\dbch\af31501 \sbasedon10 \slink8 \ssemihidden \spriority9 \styrsid15678446 Heading 8 Char;}{\*\cs23 \additive \rtlch\fcs1 \af31503 \ltrch\fcs0 \cf21\dbch\af31501 +\sbasedon10 \slink9 \ssemihidden \spriority9 \styrsid15678446 Heading 9 Char;}{\s24\ql \li0\ri0\sa80\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\contextualspace \rtlch\fcs1 \af31503\afs56\alang1025 \ltrch\fcs0 +\fs56\expnd-2\expndtw-10\lang1033\langfe1033\kerning28\loch\f31502\hich\af31502\dbch\af31501\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink25 \sqformat \spriority10 \styrsid15678446 Title;}{\*\cs25 \additive \rtlch\fcs1 \af31503\afs56 +\ltrch\fcs0 \fs56\expnd-2\expndtw-10\kerning28\loch\f31502\hich\af31502\dbch\af31501 \sbasedon10 \slink24 \spriority10 \styrsid15678446 Title Char;}{\s26\ql \li0\ri0\sa160\sl278\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\ilvl1\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31503\afs28\alang1025 \ltrch\fcs0 \fs28\expnd3\expndtw15\cf20\lang1033\langfe1033\kerning2\loch\f31506\hich\af31506\dbch\af31501\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext0 \slink27 \sqformat \spriority11 \styrsid15678446 Subtitle;}{\*\cs27 \additive \rtlch\fcs1 \af31503\afs28 \ltrch\fcs0 \fs28\expnd3\expndtw15\cf20\dbch\af31501 \sbasedon10 \slink26 \spriority11 \styrsid15678446 Subtitle Char;}{ +\s28\qc \li0\ri0\sb160\sa160\sl278\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \ai\af31507\afs24\alang1025 \ltrch\fcs0 \i\f31506\fs24\cf22\lang1033\langfe1033\kerning2\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext0 \slink29 \sqformat \spriority29 \styrsid15678446 Quote;}{\*\cs29 \additive \rtlch\fcs1 \ai\af0 \ltrch\fcs0 \i\cf22 \sbasedon10 \slink28 \spriority29 \styrsid15678446 Quote Char;}{\s30\ql \li720\ri0\sa160\sl278\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin720\itap0\contextualspace \rtlch\fcs1 \af31507\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\kerning2\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext30 \sqformat \spriority34 \styrsid15678446 List Paragraph;}{\*\cs31 \additive \rtlch\fcs1 \ai\af0 \ltrch\fcs0 \i\cf19 \sbasedon10 \sqformat \spriority21 \styrsid15678446 Intense Emphasis;}{\s32\qc \li864\ri864\sb360\sa360\sl278\slmult1 +\widctlpar\brdrt\brdrs\brdrw10\brsp200\brdrcf19 \brdrb\brdrs\brdrw10\brsp200\brdrcf19 \wrapdefault\aspalpha\aspnum\faauto\adjustright\rin864\lin864\itap0 \rtlch\fcs1 \ai\af31507\afs24\alang1025 \ltrch\fcs0 +\i\f31506\fs24\cf19\lang1033\langfe1033\kerning2\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 \slink33 \sqformat \spriority30 \styrsid15678446 Intense Quote;}{\*\cs33 \additive \rtlch\fcs1 \ai\af0 \ltrch\fcs0 \i\cf19 +\sbasedon10 \slink32 \spriority30 \styrsid15678446 Intense Quote Char;}{\*\cs34 \additive \rtlch\fcs1 \ab\af0 \ltrch\fcs0 \b\scaps\expnd1\expndtw5\cf19 \sbasedon10 \sqformat \spriority32 \styrsid15678446 Intense Reference;}}{\*\rsidtbl \rsid3543682 +\rsid6316520\rsid7364952\rsid8278432\rsid9589131\rsid10298217\rsid15678446\rsid15953651}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Adam Fourney} +{\operator Adam Fourney}{\creatim\yr2025\mo2\dy9\hr22\min56}{\revtim\yr2025\mo2\dy9\hr22\min58}{\version1}{\edmins2}{\nofpages1}{\nofwords17}{\nofchars98}{\nofcharsws114}{\vern115}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordm +l}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen +\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 +\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct +\asianbrkrule\rsidroot15678446\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 +{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang +{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} +\pard\plain \ltrpar\s24\ql \li0\ri0\sa80\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid15678446\contextualspace \rtlch\fcs1 \af31503\afs56\alang1025 \ltrch\fcs0 +\fs56\expnd-2\expndtw-10\lang1033\langfe1033\kerning28\loch\af31502\hich\af31502\dbch\af31501\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af31503 \ltrch\fcs0 \insrsid15678446 \hich\af31502\dbch\af31501\loch\f31502 This is a +\hich\af31502\dbch\af31501\loch\f31502 S\hich\af31502\dbch\af31501\loch\f31502 ample RT\hich\af31502\dbch\af31501\loch\f31502 F \hich\af31502\dbch\af31501\loch\f31502 File}{\rtlch\fcs1 \af31503 \ltrch\fcs0 \insrsid8278432 +\par }\pard\plain \ltrpar\ql \li0\ri0\sa160\sl278\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\kerning2\cgrid\langnp1033\langfenp1033 { +\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid15678446 +\par It is included to test if the MarkItDown sample plugin can correctly convert RTF files. +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100d3d1e707f007000012220000160000007468656d652f7468656d652f +7468656d65312e786d6cec5a4b8fdbc811be07c87f20789745ea414903cb0b3d3d6bcfd8034b76b0c796d812dbd36413ecd6cc080b0381f794cb020b6c825c02 +e496431064812c90452ef931066c249b1f91ea2645754b2dcf030662043373215b5f557f5d555d556cf2e1175731752e70c6094bbaaeffc0731d9ccc59489265 +d77d391d57daaec3054a42445982bbee1a73f78b47bffcc5437424221c6307e4137e84ba6e24447a54adf2390c23fe80a53881df162c8b9180db6c590d337409 +7a635aad795e508d11495c274131a87dbe58903976a652a5fb68a37c44e136115c0ecc693691aab121a1b0e1b92f117ccd0734732e10edba304fc82ea7f84ab8 +0e455cc00f5dd7537f6ef5d1c32a3a2a84a83820abc98dd55f21570884e7353567b69c95937aa35abbe197fa15808a7ddca82dff4b7d0a80e6735869ce45d7e9 +3703af5d2bb01a28bfb4e8eeb4fcba89d7f4d7f738fb9da05f6b18fa1528d7dfd8c37be3ce68d834f00a94e39b7bf89e57eb77ea065e81727cb0876f8c7aadda +c8c02b50444972be8f0e5aed7650a04bc882d1632bbc13045e6b58c0b728888632bae4140b968843b116a3d72c1b03400229122471c43ac50b348728eea58271 +6748784ad1da755294300ec35ecdf721f41a5eadfc571647471869d2921730e17b43928fc3e7194945d77d025a5d0df2fea79fdebdfdf1dddbbfbffbe69b776f +ffea9c906524725586dc314a96badccf7ffaee3f7ff8b5f3efbffdf1e7ef7f6bc7731dffe12fbff9f08f7f7e4c3d6cb5ad29deffee870f3ffef0fef7dffeebcf +df5bb4f73234d3e1531263ee3cc397ce0b16c30295294cfe7896dd4e621a21a24bf49225470992b358f48f4464a09fad1145165c1f9b767c9541aab1011faf5e +1b842751b612c4a2f169141bc053c6689f65562b3c957369669eae92a57df26ca5e35e2074619b7b8012c3cba3550a3996d8540e226cd03ca328116889132c1c +f91b3bc7d8b2baaf0831ec7a4ae619e36c219caf88d347c46a92299919d1b4153a2631f8656d2308fe366c73facae9336a5bf5105f9848d81b885ac84f3135cc +f818ad048a6d2aa728a6bac14f90886c2427eb6caee3465c80a7979832671462ce6d32cf3358afe6f4a708b29bd5eda7741d9bc84c90739bce13c4988e1cb2f3 +4184e2d4869d9024d2b15ff2730851e49c3161839f327387c87bf0034a0ebafb15c186bbafcf062f21cbe994b601227f5965165f3ec6cc88dfc99a2e10b6a59a +5e161b29b697116b74f4574b23b44f30a6e81285183b2fbfb430e8b3d4b0f996f49308b2ca31b605d61364c6aabc4f30875e493637fb79f2847023642778c90e +f0395def249e354a62941dd2fc0cbcaedb7c34cb60335a283ca7f3731df88c400f08f16235ca730e3ab4e03ea8f52c42460193f7dc1eafebccf0df4df618eccb +d7068d1bec4b90c1b79681c4aecb7cd43653448d09b6013345c439b1a55b1031dcbf1591c55589adac720b73d36edd00dd91d1f4c424b9a603fadf743e9640fc +343d8f5db191b06ed9ed1c4a28c73b3dce21dc6e67336059483effc6668856c919865ab29fb5eefb9afbbec6fdbfef6b0eede7fb6ee650cf71dfcdb8d065dc77 +33c501cba7e966b60d0cf436f290213fec51473ff1c1939f05a17422d6149f7075f8c3e199261cc3a09453a79eb83c094c23b894650e263070cb0c29192763e2 +5744449308a57042e4bb52c99217aa97dc4919878323356cd52df174159fb2303ff054274c5e5e593912db71af09474ff9381c56891c1db48a41c94f9daa025f +c576a90e5b3704a4ec6d4868939924ea1612adcde03524e4d9d9a761d1b1b0684bf51b57ed9902a8955e81876e071ed5bb6eb32109c149399f43831e4a3fe5ae +de785739f3537afa90318d0880c3c57c2570345f7aba23b91e5c9e5c5d1e6a37f0b4414239250f2b9384b28c6af078048fc24574cad19bd0b8adaf3b5b971af4 +a429d47c10df5b1aadf6c758dcd5d720b79b1b68a2670a9a38975d37a8372164e628edba0b383886cb3885d8e1f2b90bd125bc7d998b2cdff077c92c69c6c510 +f12837b84a3ab97b622270e65012775db9fcd20d3451394471f36b90103e5b721d482b9f1b3970bae964bc58e0b9d0ddae8d484be7b790e1f35c61fd5589df1d +2c25d90adc3d89c24b674657d90b0421d66cf9d28021e1f0fec0cfad191278215626b26dfced14a622f9eb6fa4540ce5e388a6112a2a8a9ecc73b8aa27251d75 +57da40bb2bd60c06d54c5214c2d9521658dda846352d4b57cee160d5bd5e485a4e4b9adb9a6964155935ed59cc98615306766c79b722afb1da9818729a5ee1f3 +d4bd9b723b9b5cb7d3279455020c5edaef6ea55fa3b69dcca02619efa76199b38b51b3766c16780db59b14092deb071bb53b762b6b84753a18bc53e507b9dda8 +85a1c5a6af5496566fcef597db6cf61a92c710badc15cd5f77d304ee6454f2f42c53be9db1705d5c529e279adce7b22795489abcc00b8784579b7eb2746fbe3d +f257ae7ed10c28b41493b5ab14b4367ba6608197a2f986bd8d7029a16686d6bb1456c78ab67e575c6d28cb561df0ca843c5f3598b6b0145ced5b118ec83304ad +ed44357679ee05da57a2c82f70e5ac32d275bff69abdc6a0d61c54bc76735469d41b5ea5ddecd52bbd66b3ee8f9abe37ecd7de003d11c57e33fff4610c6f82e8 +baf800428def7d04116f5e763d98b3b8cad4470e55e57df511845f3bfc110438126805b571a7dee907954ebd37ae3486fd76a53308fa956130680dc7c341b3dd +19bf719d0b056ef4ea8346306a57027f30a834024fd26f772aad46add66bb47aed51a3f7a6703fac3ccfc1852dc07c8ad7a3ff020000ffff0300504b03041400 +06000800000021000dd1909fb60000001b010000270000007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c7384 +8f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16d +b8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017c +c524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d001400060008 +0000002100e9de0fbfff0000001c0200001300000000000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600 +080000002100a5d6a7e7c0000000360100000b00000000000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b +799616830000008a0000001c00000000000000000000000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014 +000600080000002100d3d1e707f0070000122200001600000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01 +022d00140006000800000021000dd1909fb60000001b0100002700000000000000000000000000fa0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000f50b00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text; +\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; +\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; +\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; +\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; +\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; +\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; +\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; +\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; +\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; +\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; +\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; +\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; +\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; +\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; +\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore 01050000 +02000000180000004d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000f0af +5b31897bdb01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/packages/markitdown-sample-plugin/tests/test_sample_plugin.py b/packages/markitdown-sample-plugin/tests/test_sample_plugin.py new file mode 100644 index 0000000..49d54aa --- /dev/null +++ b/packages/markitdown-sample-plugin/tests/test_sample_plugin.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 -m pytest +import os +import pytest + +from markitdown import MarkItDown +from markitdown_sample_plugin import RtfConverter + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "test_files") + +RTF_TEST_STRINGS = { + "This is a Sample RTF File", + "It is included to test if the MarkItDown sample plugin can correctly convert RTF files.", +} + + +def test_converter() -> None: + """Tests the RTF converter dirctly.""" + converter = RtfConverter() + result = converter.convert( + os.path.join(TEST_FILES_DIR, "test.rtf"), file_extension=".rtf" + ) + + for test_string in RTF_TEST_STRINGS: + assert test_string in result.text_content + + +def test_markitdown() -> None: + """Tests that MarkItDown correctly loads the plugin.""" + md = MarkItDown() + result = md.convert(os.path.join(TEST_FILES_DIR, "test.rtf")) + + for test_string in RTF_TEST_STRINGS: + assert test_string in result.text_content + + +if __name__ == "__main__": + """Runs this file's tests from the command line.""" + test_converter() + test_markitdown() + print("All tests passed.") diff --git a/packages/markitdown/README.md b/packages/markitdown/README.md new file mode 100644 index 0000000..54453ab --- /dev/null +++ b/packages/markitdown/README.md @@ -0,0 +1,52 @@ +# MarkItDown + +> [!IMPORTANT] +> MarkItDown is a Python package and command-line utility for converting various files to Markdown (e.g., for indexing, text analysis, etc). +> +> For more information, and full documentation, see the project [README.md](https://github.com/microsoft/markitdown) on GitHub. + +## Installation + +From PyPI: + +```bash +pip install markitdown +``` + +From source: + +```bash +git clone git@github.com:microsoft/markitdown.git +cd markitdown +pip install -e packages/markitdown +``` + +## Usage + +### Command-Line + +```bash +markitdown path-to-file.pdf > document.md +``` + +### Python API + +```python +from markitdown import MarkItDown + +md = MarkItDown() +result = md.convert("test.xlsx") +print(result.text_content) +``` + +### More Information + +For more information, and full documentation, see the project [README.md](https://github.com/microsoft/markitdown) on GitHub. + +## Trademarks + +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. diff --git a/pyproject.toml b/packages/markitdown/pyproject.toml similarity index 100% rename from pyproject.toml rename to packages/markitdown/pyproject.toml diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py new file mode 100644 index 0000000..dc5aafc --- /dev/null +++ b/packages/markitdown/src/markitdown/__about__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT +__version__ = "0.0.2a1" diff --git a/packages/markitdown/src/markitdown/__init__.py b/packages/markitdown/src/markitdown/__init__.py new file mode 100644 index 0000000..5407233 --- /dev/null +++ b/packages/markitdown/src/markitdown/__init__.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT + +from ._markitdown import MarkItDown +from ._exceptions import ( + MarkItDownException, + ConverterPrerequisiteException, + FileConversionException, + UnsupportedFormatException, +) +from .converters import DocumentConverter, DocumentConverterResult + +__all__ = [ + "MarkItDown", + "DocumentConverter", + "DocumentConverterResult", + "MarkItDownException", + "ConverterPrerequisiteException", + "FileConversionException", + "UnsupportedFormatException", +] diff --git a/src/markitdown/__main__.py b/packages/markitdown/src/markitdown/__main__.py similarity index 67% rename from src/markitdown/__main__.py rename to packages/markitdown/src/markitdown/__main__.py index 353be84..6a24391 100644 --- a/src/markitdown/__main__.py +++ b/packages/markitdown/src/markitdown/__main__.py @@ -4,6 +4,7 @@ import argparse import sys from textwrap import dedent +from importlib.metadata import entry_points from .__about__ import __version__ from ._markitdown import MarkItDown, DocumentConverterResult @@ -71,9 +72,39 @@ def main(): help="Document Intelligence Endpoint. Required if using Document Intelligence.", ) + parser.add_argument( + "-p", + "--use-plugins", + action="store_true", + help="Use 3rd-party plugins to convert files. Use --list-plugins to see installed plugins.", + ) + + parser.add_argument( + "--list-plugins", + action="store_true", + help="List installed 3rd-party plugins. Plugins are loaded when using the -p or --use-plugin option.", + ) + parser.add_argument("filename", nargs="?") args = parser.parse_args() + if args.list_plugins: + # List installed plugins, then exit + print("Installed MarkItDown 3rd-party Plugins:\n") + plugin_entry_points = list(entry_points(group="markitdown.plugin")) + if len(plugin_entry_points) == 0: + print(" * No 3rd-party plugins installed.") + print( + "\nFind plugins by searching for the hashtag #markitdown-plugin on GitHub.\n" + ) + else: + for entry_point in plugin_entry_points: + print(f" * {entry_point.name:<16}\t(package: {entry_point.value})") + print( + "\nUse the -p (or --use-plugins) option to enable 3rd-party plugins.\n" + ) + sys.exit(0) + if args.use_docintel: if args.endpoint is None: raise ValueError( @@ -81,9 +112,11 @@ def main(): ) elif args.filename is None: raise ValueError("Filename is required when using Document Intelligence.") - markitdown = MarkItDown(docintel_endpoint=args.endpoint) + markitdown = MarkItDown( + enable_plugins=args.use_plugins, docintel_endpoint=args.endpoint + ) else: - markitdown = MarkItDown() + markitdown = MarkItDown(enable_plugins=args.use_plugins) if args.filename is None: result = markitdown.convert_stream(sys.stdin.buffer) diff --git a/packages/markitdown/src/markitdown/_exceptions.py b/packages/markitdown/src/markitdown/_exceptions.py new file mode 100644 index 0000000..30c4dc5 --- /dev/null +++ b/packages/markitdown/src/markitdown/_exceptions.py @@ -0,0 +1,37 @@ +class MarkItDownException(BaseException): + """ + Base exception class for MarkItDown. + """ + + pass + + +class ConverterPrerequisiteException(MarkItDownException): + """ + Thrown when instantiating a DocumentConverter in cases where + a required library or dependency is not installed, an API key + is not set, or some other prerequisite is not met. + + This is not necessarily a fatal error. If thrown during + MarkItDown's plugin loading phase, the converter will simply be + skipped, and a warning will be issued. + """ + + pass + + +class FileConversionException(MarkItDownException): + """ + Thrown when a suitable converter was found, but the conversion + process fails for any reason. + """ + + pass + + +class UnsupportedFormatException(MarkItDownException): + """ + Thrown when no suitable converter was found for the given file. + """ + + pass diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py new file mode 100644 index 0000000..b7ac5bc --- /dev/null +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -0,0 +1,440 @@ +import copy +import mimetypes +import os +import re +import tempfile +import warnings +import traceback +from importlib.metadata import entry_points +from typing import Any, List, Optional, Union +from pathlib import Path +from urllib.parse import urlparse +from warnings import warn + +# File-format detection +import puremagic +import requests + +from .converters import ( + DocumentConverter, + DocumentConverterResult, + PlainTextConverter, + HtmlConverter, + RssConverter, + WikipediaConverter, + YouTubeConverter, + IpynbConverter, + BingSerpConverter, + PdfConverter, + DocxConverter, + XlsxConverter, + XlsConverter, + PptxConverter, + ImageConverter, + WavConverter, + Mp3Converter, + OutlookMsgConverter, + ZipConverter, + DocumentIntelligenceConverter, +) + +from ._exceptions import ( + FileConversionException, + UnsupportedFormatException, + ConverterPrerequisiteException, +) + +# Override mimetype for csv to fix issue on windows +mimetypes.add_type("text/csv", ".csv") + +PRIORITY_SPECIFIC_FILE_FORMAT = 0.0 +PRIORITY_GENERIC_FILE_FORMAT = 10.0 + + +_plugins: Union[None | List[Any]] = None + + +def _load_plugins() -> Union[None | List[Any]]: + """Lazy load plugins, exiting early if already loaded.""" + global _plugins + + # Skip if we've already loaded plugins + if _plugins is not None: + return _plugins + + # Load plugins + _plugins = [] + for entry_point in entry_points(group="markitdown.plugin"): + try: + _plugins.append(entry_point.load()) + except Exception: + tb = traceback.format_exc() + warn(f"Plugin '{entry_point.name}' failed to load ... skipping:\n{tb}") + + return _plugins + + +class MarkItDown: + """(In preview) An extremely simple text-based document reader, suitable for LLM use. + This reader will convert common file-types or webpages to Markdown.""" + + def __init__( + self, + *, + enable_builtins: Union[None, bool] = None, + enable_plugins: Union[None, bool] = None, + **kwargs, + ): + self._builtins_enabled = False + self._plugins_enabled = False + + requests_session = kwargs.get("requests_session") + if requests_session is None: + self._requests_session = requests.Session() + else: + self._requests_session = requests_session + + # TODO - remove these (see enable_builtins) + self._llm_client = None + self._llm_model = None + self._exiftool_path = None + self._style_map = None + + # Register the converters + self._page_converters: List[DocumentConverter] = [] + + if ( + enable_builtins is None or enable_builtins + ): # Default to True when not specified + self.enable_builtins(**kwargs) + + if enable_plugins: + self.enable_plugins(**kwargs) + + def enable_builtins(self, **kwargs) -> None: + """ + Enable and register built-in converters. + Built-in converters are enabled by default. + This method should only be called once, if built-ins were initially disabled. + """ + if not self._builtins_enabled: + # TODO: Move these into converter constructors + self._llm_client = kwargs.get("llm_client") + self._llm_model = kwargs.get("llm_model") + self._exiftool_path = kwargs.get("exiftool_path") + self._style_map = kwargs.get("style_map") + + # Register converters for successful browsing operations + # Later registrations are tried first / take higher priority than earlier registrations + # To this end, the most specific converters should appear below the most generic converters + self.register_converter(PlainTextConverter()) + self.register_converter(ZipConverter()) + self.register_converter(HtmlConverter()) + self.register_converter(RssConverter()) + self.register_converter(WikipediaConverter()) + self.register_converter(YouTubeConverter()) + self.register_converter(BingSerpConverter()) + self.register_converter(DocxConverter()) + self.register_converter(XlsxConverter()) + self.register_converter(XlsConverter()) + self.register_converter(PptxConverter()) + self.register_converter(WavConverter()) + self.register_converter(Mp3Converter()) + self.register_converter(ImageConverter()) + self.register_converter(IpynbConverter()) + self.register_converter(PdfConverter()) + self.register_converter(OutlookMsgConverter()) + + # Register Document Intelligence converter at the top of the stack if endpoint is provided + docintel_endpoint = kwargs.get("docintel_endpoint") + if docintel_endpoint is not None: + self.register_converter( + DocumentIntelligenceConverter(endpoint=docintel_endpoint) + ) + + self._builtins_enabled = True + else: + warn("Built-in converters are already enabled.", RuntimeWarning) + + def enable_plugins(self, **kwargs) -> None: + """ + Enable and register converters provided by plugins. + Plugins are disabled by default. + This method should only be called once, if plugins were initially disabled. + """ + if not self._plugins_enabled: + # Load plugins + for plugin in _load_plugins(): + try: + plugin.register_converters(self, **kwargs) + except Exception: + tb = traceback.format_exc() + warn(f"Plugin '{plugin}' failed to register converters:\n{tb}") + self._plugins_enabled = True + else: + warn("Plugins converters are already enabled.", RuntimeWarning) + + def convert( + self, source: Union[str, requests.Response, Path], **kwargs: Any + ) -> DocumentConverterResult: # TODO: deal with kwargs + """ + Args: + - source: can be a string representing a path either as string pathlib path object or url, or a requests.response object + - extension: specifies the file extension to use when interpreting the file. If None, infer from source (path, uri, content-type, etc.) + """ + + # Local path or url + if isinstance(source, str): + if ( + source.startswith("http://") + or source.startswith("https://") + or source.startswith("file://") + ): + return self.convert_url(source, **kwargs) + else: + return self.convert_local(source, **kwargs) + # Request response + elif isinstance(source, requests.Response): + return self.convert_response(source, **kwargs) + elif isinstance(source, Path): + return self.convert_local(source, **kwargs) + + def convert_local( + self, path: Union[str, Path], **kwargs: Any + ) -> DocumentConverterResult: # TODO: deal with kwargs + if isinstance(path, Path): + path = str(path) + # Prepare a list of extensions to try (in order of priority) + ext = kwargs.get("file_extension") + extensions = [ext] if ext is not None else [] + + # Get extension alternatives from the path and puremagic + base, ext = os.path.splitext(path) + self._append_ext(extensions, ext) + + for g in self._guess_ext_magic(path): + self._append_ext(extensions, g) + + # Convert + return self._convert(path, extensions, **kwargs) + + # TODO what should stream's type be? + def convert_stream( + self, stream: Any, **kwargs: Any + ) -> DocumentConverterResult: # TODO: deal with kwargs + # Prepare a list of extensions to try (in order of priority) + ext = kwargs.get("file_extension") + extensions = [ext] if ext is not None else [] + + # Save the file locally to a temporary file. It will be deleted before this method exits + handle, temp_path = tempfile.mkstemp() + fh = os.fdopen(handle, "wb") + result = None + try: + # Write to the temporary file + content = stream.read() + if isinstance(content, str): + fh.write(content.encode("utf-8")) + else: + fh.write(content) + fh.close() + + # Use puremagic to check for more extension options + for g in self._guess_ext_magic(temp_path): + self._append_ext(extensions, g) + + # Convert + result = self._convert(temp_path, extensions, **kwargs) + # Clean up + finally: + try: + fh.close() + except Exception: + pass + os.unlink(temp_path) + + return result + + def convert_url( + self, url: str, **kwargs: Any + ) -> DocumentConverterResult: # TODO: fix kwargs type + # Send a HTTP request to the URL + response = self._requests_session.get(url, stream=True) + response.raise_for_status() + return self.convert_response(response, **kwargs) + + def convert_response( + self, response: requests.Response, **kwargs: Any + ) -> DocumentConverterResult: # TODO fix kwargs type + # Prepare a list of extensions to try (in order of priority) + ext = kwargs.get("file_extension") + extensions = [ext] if ext is not None else [] + + # Guess from the mimetype + content_type = response.headers.get("content-type", "").split(";")[0] + self._append_ext(extensions, mimetypes.guess_extension(content_type)) + + # Read the content disposition if there is one + content_disposition = response.headers.get("content-disposition", "") + m = re.search(r"filename=([^;]+)", content_disposition) + if m: + base, ext = os.path.splitext(m.group(1).strip("\"'")) + self._append_ext(extensions, ext) + + # Read from the extension from the path + base, ext = os.path.splitext(urlparse(response.url).path) + self._append_ext(extensions, ext) + + # Save the file locally to a temporary file. It will be deleted before this method exits + handle, temp_path = tempfile.mkstemp() + fh = os.fdopen(handle, "wb") + result = None + try: + # Download the file + for chunk in response.iter_content(chunk_size=512): + fh.write(chunk) + fh.close() + + # Use puremagic to check for more extension options + for g in self._guess_ext_magic(temp_path): + self._append_ext(extensions, g) + + # Convert + result = self._convert(temp_path, extensions, url=response.url, **kwargs) + # Clean up + finally: + try: + fh.close() + except Exception: + pass + os.unlink(temp_path) + + return result + + def _convert( + self, local_path: str, extensions: List[Union[str, None]], **kwargs + ) -> DocumentConverterResult: + error_trace = "" + + # Create a copy of the page_converters list, sorted by priority. + # We do this with each call to _convert because the priority of converters may change between calls. + # The sort is guaranteed to be stable, so converters with the same priority will remain in the same order. + sorted_converters = sorted(self._page_converters, key=lambda x: x.priority) + + for ext in extensions + [None]: # Try last with no extension + for converter in sorted_converters: + _kwargs = copy.deepcopy(kwargs) + + # Overwrite file_extension appropriately + if ext is None: + if "file_extension" in _kwargs: + del _kwargs["file_extension"] + else: + _kwargs.update({"file_extension": ext}) + + # Copy any additional global options + if "llm_client" not in _kwargs and self._llm_client is not None: + _kwargs["llm_client"] = self._llm_client + + if "llm_model" not in _kwargs and self._llm_model is not None: + _kwargs["llm_model"] = self._llm_model + + if "style_map" not in _kwargs and self._style_map is not None: + _kwargs["style_map"] = self._style_map + + if "exiftool_path" not in _kwargs and self._exiftool_path is not None: + _kwargs["exiftool_path"] = self._exiftool_path + + # Add the list of converters for nested processing + _kwargs["_parent_converters"] = self._page_converters + + # If we hit an error log it and keep trying + # try: + if True: + res = converter.convert(local_path, **_kwargs) + # except Exception: + # error_trace = ("\n\n" + traceback.format_exc()).strip() + + if res is not None: + # Normalize the content + res.text_content = "\n".join( + [line.rstrip() for line in re.split(r"\r?\n", res.text_content)] + ) + res.text_content = re.sub(r"\n{3,}", "\n\n", res.text_content) + + # Todo + return res + + # If we got this far without success, report any exceptions + if len(error_trace) > 0: + raise FileConversionException( + f"Could not convert '{local_path}' to Markdown. File type was recognized as {extensions}. While converting the file, the following error was encountered:\n\n{error_trace}" + ) + + # Nothing can handle it! + raise UnsupportedFormatException( + f"Could not convert '{local_path}' to Markdown. The formats {extensions} are not supported." + ) + + def _append_ext(self, extensions, ext): + """Append a unique non-None, non-empty extension to a list of extensions.""" + if ext is None: + return + ext = ext.strip() + if ext == "": + return + # if ext not in extensions: + extensions.append(ext) + + def _guess_ext_magic(self, path): + """Use puremagic (a Python implementation of libmagic) to guess a file's extension based on the first few bytes.""" + # Use puremagic to guess + try: + guesses = puremagic.magic_file(path) + + # Fix for: https://github.com/microsoft/markitdown/issues/222 + # If there are no guesses, then try again after trimming leading ASCII whitespaces. + # ASCII whitespace characters are those byte values in the sequence b' \t\n\r\x0b\f' + # (space, tab, newline, carriage return, vertical tab, form feed). + if len(guesses) == 0: + with open(path, "rb") as file: + while True: + char = file.read(1) + if not char: # End of file + break + if not char.isspace(): + file.seek(file.tell() - 1) + break + try: + guesses = puremagic.magic_stream(file) + except puremagic.main.PureError: + pass + + extensions = list() + for g in guesses: + ext = g.extension.strip() + if len(ext) > 0: + if not ext.startswith("."): + ext = "." + ext + if ext not in extensions: + extensions.append(ext) + return extensions + except FileNotFoundError: + pass + except IsADirectoryError: + pass + except PermissionError: + pass + return [] + + def register_page_converter(self, converter: DocumentConverter) -> None: + """DEPRECATED: User register_converter instead.""" + warn( + "register_page_converter is deprecated. Use register_converter instead.", + DeprecationWarning, + ) + self.register_converter(converter) + + def register_converter(self, converter: DocumentConverter) -> None: + """Register a page text converter.""" + self._page_converters.insert(0, converter) diff --git a/packages/markitdown/src/markitdown/converters/__init__.py b/packages/markitdown/src/markitdown/converters/__init__.py new file mode 100644 index 0000000..1e5afe4 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/__init__.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT + +from ._base import DocumentConverter, DocumentConverterResult +from ._plain_text_converter import PlainTextConverter +from ._html_converter import HtmlConverter +from ._rss_converter import RssConverter +from ._wikipedia_converter import WikipediaConverter +from ._youtube_converter import YouTubeConverter +from ._ipynb_converter import IpynbConverter +from ._bing_serp_converter import BingSerpConverter +from ._pdf_converter import PdfConverter +from ._docx_converter import DocxConverter +from ._xlsx_converter import XlsxConverter, XlsConverter +from ._pptx_converter import PptxConverter +from ._image_converter import ImageConverter +from ._wav_converter import WavConverter +from ._mp3_converter import Mp3Converter +from ._outlook_msg_converter import OutlookMsgConverter +from ._zip_converter import ZipConverter +from ._doc_intel_converter import DocumentIntelligenceConverter + +__all__ = [ + "DocumentConverter", + "DocumentConverterResult", + "PlainTextConverter", + "HtmlConverter", + "RssConverter", + "WikipediaConverter", + "YouTubeConverter", + "IpynbConverter", + "BingSerpConverter", + "PdfConverter", + "DocxConverter", + "XlsxConverter", + "XlsConverter", + "PptxConverter", + "ImageConverter", + "WavConverter", + "Mp3Converter", + "OutlookMsgConverter", + "ZipConverter", + "DocumentIntelligenceConverter", +] diff --git a/packages/markitdown/src/markitdown/converters/_base.py b/packages/markitdown/src/markitdown/converters/_base.py new file mode 100644 index 0000000..6d0a5a4 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_base.py @@ -0,0 +1,34 @@ +from typing import Any, Union + + +class DocumentConverterResult: + """The result of converting a document to text.""" + + def __init__(self, title: Union[str, None] = None, text_content: str = ""): + self.title: Union[str, None] = title + self.text_content: str = text_content + + +class DocumentConverter: + """Abstract superclass of all DocumentConverters.""" + + def __init__(self, priority: float = 0.0): + self._priority = priority + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + raise NotImplementedError("Subclasses must implement this method") + + @property + def priority(self) -> float: + """Priority of the converter in markitdown's converter list. Higher priority values are tried first.""" + return self._priority + + @priority.setter + def radius(self, value: float): + self._priority = value + + @priority.deleter + def radius(self): + raise AttributeError("Cannot delete the priority attribute") diff --git a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py new file mode 100644 index 0000000..b903724 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py @@ -0,0 +1,81 @@ +# type: ignore +import base64 +import re + +from typing import Union +from urllib.parse import parse_qs, urlparse +from bs4 import BeautifulSoup + +from ._base import DocumentConverter, DocumentConverterResult +from ._markdownify import _CustomMarkdownify + + +class BingSerpConverter(DocumentConverter): + """ + Handle Bing results pages (only the organic search results). + NOTE: It is better to use the Bing API + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a Bing SERP + extension = kwargs.get("file_extension", "") + if extension.lower() not in [".html", ".htm"]: + return None + url = kwargs.get("url", "") + if not re.search(r"^https://www\.bing\.com/search\?q=", url): + return None + + # Parse the query parameters + parsed_params = parse_qs(urlparse(url).query) + query = parsed_params.get("q", [""])[0] + + # Parse the file + soup = None + with open(local_path, "rt", encoding="utf-8") as fh: + soup = BeautifulSoup(fh.read(), "html.parser") + + # Clean up some formatting + for tptt in soup.find_all(class_="tptt"): + if hasattr(tptt, "string") and tptt.string: + tptt.string += " " + for slug in soup.find_all(class_="algoSlug_icon"): + slug.extract() + + # Parse the algorithmic results + _markdownify = _CustomMarkdownify() + results = list() + for result in soup.find_all(class_="b_algo"): + # Rewrite redirect urls + for a in result.find_all("a", href=True): + parsed_href = urlparse(a["href"]) + qs = parse_qs(parsed_href.query) + + # The destination is contained in the u parameter, + # but appears to be base64 encoded, with some prefix + if "u" in qs: + u = ( + qs["u"][0][2:].strip() + "==" + ) # Python 3 doesn't care about extra padding + + try: + # RFC 4648 / Base64URL" variant, which uses "-" and "_" + a["href"] = base64.b64decode(u, altchars="-_").decode("utf-8") + except UnicodeDecodeError: + pass + except binascii.Error: + pass + + # Convert to markdown + md_result = _markdownify.convert_soup(result).strip() + lines = [line.strip() for line in re.split(r"\n+", md_result)] + results.append("\n".join([line for line in lines if len(line) > 0])) + + webpage_text = ( + f"## A Bing search for '{query}' found the following results:\n\n" + + "\n\n".join(results) + ) + + return DocumentConverterResult( + title=None if soup.title is None else soup.title.string, + text_content=webpage_text, + ) diff --git a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py new file mode 100644 index 0000000..94acc9f --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py @@ -0,0 +1,85 @@ +from typing import Any, Union + +# Azure imports +from azure.ai.documentintelligence import DocumentIntelligenceClient +from azure.ai.documentintelligence.models import ( + AnalyzeDocumentRequest, + AnalyzeResult, + DocumentAnalysisFeature, +) +from azure.identity import DefaultAzureCredential + +from ._base import DocumentConverter, DocumentConverterResult + + +# TODO: currently, there is a bug in the document intelligence SDK with importing the "ContentFormat" enum. +# This constant is a temporary fix until the bug is resolved. +CONTENT_FORMAT = "markdown" + + +class DocumentIntelligenceConverter(DocumentConverter): + """Specialized DocumentConverter that uses Document Intelligence to extract text from documents.""" + + def __init__( + self, + endpoint: str, + api_version: str = "2024-07-31-preview", + ): + self.endpoint = endpoint + self.api_version = api_version + self.doc_intel_client = DocumentIntelligenceClient( + endpoint=self.endpoint, + api_version=self.api_version, + credential=DefaultAzureCredential(), + ) + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if extension is not supported by Document Intelligence + extension = kwargs.get("file_extension", "") + docintel_extensions = [ + ".pdf", + ".docx", + ".xlsx", + ".pptx", + ".html", + ".jpeg", + ".jpg", + ".png", + ".bmp", + ".tiff", + ".heif", + ] + if extension.lower() not in docintel_extensions: + return None + + # Get the bytestring for the local path + with open(local_path, "rb") as f: + file_bytes = f.read() + + # Certain document analysis features are not availiable for filetypes (.xlsx, .pptx, .html) + if extension.lower() in [".xlsx", ".pptx", ".html"]: + analysis_features = [] + else: + analysis_features = [ + DocumentAnalysisFeature.FORMULAS, # enable formula extraction + DocumentAnalysisFeature.OCR_HIGH_RESOLUTION, # enable high resolution OCR + DocumentAnalysisFeature.STYLE_FONT, # enable font style extraction + ] + + # Extract the text using Azure Document Intelligence + poller = self.doc_intel_client.begin_analyze_document( + model_id="prebuilt-layout", + body=AnalyzeDocumentRequest(bytes_source=file_bytes), + features=analysis_features, + output_content_format=CONTENT_FORMAT, # TODO: replace with "ContentFormat.MARKDOWN" when the bug is fixed + ) + result: AnalyzeResult = poller.result() + + # remove comments from the markdown content generated by Doc Intelligence and append to markdown string + markdown_text = re.sub(r"", "", result.content, flags=re.DOTALL) + return DocumentConverterResult( + title=None, + text_content=markdown_text, + ) diff --git a/packages/markitdown/src/markitdown/converters/_docx_converter.py b/packages/markitdown/src/markitdown/converters/_docx_converter.py new file mode 100644 index 0000000..fb61cca --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_docx_converter.py @@ -0,0 +1,31 @@ +from typing import Union + +import mammoth + +from ._base import ( + DocumentConverterResult, +) + +from ._html_converter import HtmlConverter + + +class DocxConverter(HtmlConverter): + """ + Converts DOCX files to Markdown. Style information (e.g.m headings) and tables are preserved where possible. + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a DOCX + extension = kwargs.get("file_extension", "") + if extension.lower() != ".docx": + return None + + result = None + with open(local_path, "rb") as docx_file: + style_map = kwargs.get("style_map", None) + + result = mammoth.convert_to_html(docx_file, style_map=style_map) + html_content = result.value + result = self._convert(html_content) + + return result diff --git a/packages/markitdown/src/markitdown/converters/_html_converter.py b/packages/markitdown/src/markitdown/converters/_html_converter.py new file mode 100644 index 0000000..ae7259e --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_html_converter.py @@ -0,0 +1,51 @@ +from typing import Any, Union +from bs4 import BeautifulSoup + +from ._base import DocumentConverter, DocumentConverterResult +from ._markdownify import _CustomMarkdownify + + +class HtmlConverter(DocumentConverter): + """Anything with content type text/html""" + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not html + extension = kwargs.get("file_extension", "") + if extension.lower() not in [".html", ".htm"]: + return None + + result = None + with open(local_path, "rt", encoding="utf-8") as fh: + result = self._convert(fh.read()) + + return result + + def _convert(self, html_content: str) -> Union[None, DocumentConverterResult]: + """Helper function that converts an HTML string.""" + + # Parse the string + soup = BeautifulSoup(html_content, "html.parser") + + # Remove javascript and style blocks + for script in soup(["script", "style"]): + script.extract() + + # Print only the main content + body_elm = soup.find("body") + webpage_text = "" + if body_elm: + webpage_text = _CustomMarkdownify().convert_soup(body_elm) + else: + webpage_text = _CustomMarkdownify().convert_soup(soup) + + assert isinstance(webpage_text, str) + + # remove leading and trailing \n + webpage_text = webpage_text.strip() + + return DocumentConverterResult( + title=None if soup.title is None else soup.title.string, + text_content=webpage_text, + ) diff --git a/packages/markitdown/src/markitdown/converters/_image_converter.py b/packages/markitdown/src/markitdown/converters/_image_converter.py new file mode 100644 index 0000000..f3dee6b --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_image_converter.py @@ -0,0 +1,87 @@ +from typing import Union +from ._base import DocumentConverterResult +from ._media_converter import MediaConverter + + +class ImageConverter(MediaConverter): + """ + Converts images to markdown via extraction of metadata (if `exiftool` is installed), OCR (if `easyocr` is installed), and description via a multimodal LLM (if an llm_client is configured). + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not an image + extension = kwargs.get("file_extension", "") + if extension.lower() not in [".jpg", ".jpeg", ".png"]: + return None + + md_content = "" + + # Add metadata + metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) + + if metadata: + for f in [ + "ImageSize", + "Title", + "Caption", + "Description", + "Keywords", + "Artist", + "Author", + "DateTimeOriginal", + "CreateDate", + "GPSPosition", + ]: + if f in metadata: + md_content += f"{f}: {metadata[f]}\n" + + # Try describing the image with GPTV + llm_client = kwargs.get("llm_client") + llm_model = kwargs.get("llm_model") + if llm_client is not None and llm_model is not None: + md_content += ( + "\n# Description:\n" + + self._get_llm_description( + local_path, + extension, + llm_client, + llm_model, + prompt=kwargs.get("llm_prompt"), + ).strip() + + "\n" + ) + + return DocumentConverterResult( + title=None, + text_content=md_content, + ) + + def _get_llm_description(self, local_path, extension, client, model, prompt=None): + if prompt is None or prompt.strip() == "": + prompt = "Write a detailed caption for this image." + + data_uri = "" + with open(local_path, "rb") as image_file: + content_type, encoding = mimetypes.guess_type("_dummy" + extension) + if content_type is None: + content_type = "image/jpeg" + image_base64 = base64.b64encode(image_file.read()).decode("utf-8") + data_uri = f"data:{content_type};base64,{image_base64}" + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": prompt}, + { + "type": "image_url", + "image_url": { + "url": data_uri, + }, + }, + ], + } + ] + + response = client.chat.completions.create(model=model, messages=messages) + return response.choices[0].message.content diff --git a/packages/markitdown/src/markitdown/converters/_ipynb_converter.py b/packages/markitdown/src/markitdown/converters/_ipynb_converter.py new file mode 100644 index 0000000..cdeb478 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_ipynb_converter.py @@ -0,0 +1,70 @@ +import json +from typing import Any, Union + +from ._base import ( + DocumentConverter, + DocumentConverterResult, +) + +from .._exceptions import FileConversionException + + +class IpynbConverter(DocumentConverter): + """Converts Jupyter Notebook (.ipynb) files to Markdown.""" + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not ipynb + extension = kwargs.get("file_extension", "") + if extension.lower() != ".ipynb": + return None + + # Parse and convert the notebook + result = None + with open(local_path, "rt", encoding="utf-8") as fh: + notebook_content = json.load(fh) + result = self._convert(notebook_content) + + return result + + def _convert(self, notebook_content: dict) -> Union[None, DocumentConverterResult]: + """Helper function that converts notebook JSON content to Markdown.""" + try: + md_output = [] + title = None + + for cell in notebook_content.get("cells", []): + cell_type = cell.get("cell_type", "") + source_lines = cell.get("source", []) + + if cell_type == "markdown": + md_output.append("".join(source_lines)) + + # Extract the first # heading as title if not already found + if title is None: + for line in source_lines: + if line.startswith("# "): + title = line.lstrip("# ").strip() + break + + elif cell_type == "code": + # Code cells are wrapped in Markdown code blocks + md_output.append(f"```python\n{''.join(source_lines)}\n```") + elif cell_type == "raw": + md_output.append(f"```\n{''.join(source_lines)}\n```") + + md_text = "\n\n".join(md_output) + + # Check for title in notebook metadata + title = notebook_content.get("metadata", {}).get("title", title) + + return DocumentConverterResult( + title=title, + text_content=md_text, + ) + + except Exception as e: + raise FileConversionException( + f"Error converting .ipynb file: {str(e)}" + ) from e diff --git a/packages/markitdown/src/markitdown/converters/_markdownify.py b/packages/markitdown/src/markitdown/converters/_markdownify.py new file mode 100644 index 0000000..5b6d739 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_markdownify.py @@ -0,0 +1,87 @@ +import re +import markdownify + +from typing import Any +from urllib.parse import quote, unquote, urlparse, urlunparse + + +class _CustomMarkdownify(markdownify.MarkdownConverter): + """ + A custom version of markdownify's MarkdownConverter. Changes include: + + - Altering the default heading style to use '#', '##', etc. + - Removing javascript hyperlinks. + - Truncating images with large data:uri sources. + - Ensuring URIs are properly escaped, and do not conflict with Markdown syntax + """ + + def __init__(self, **options: Any): + options["heading_style"] = options.get("heading_style", markdownify.ATX) + # Explicitly cast options to the expected type if necessary + super().__init__(**options) + + def convert_hn(self, n: int, el: Any, text: str, convert_as_inline: bool) -> str: + """Same as usual, but be sure to start with a new line""" + if not convert_as_inline: + if not re.search(r"^\n", text): + return "\n" + super().convert_hn(n, el, text, convert_as_inline) # type: ignore + + return super().convert_hn(n, el, text, convert_as_inline) # type: ignore + + def convert_a(self, el: Any, text: str, convert_as_inline: bool): + """Same as usual converter, but removes Javascript links and escapes URIs.""" + prefix, suffix, text = markdownify.chomp(text) # type: ignore + if not text: + return "" + href = el.get("href") + title = el.get("title") + + # Escape URIs and skip non-http or file schemes + if href: + try: + parsed_url = urlparse(href) # type: ignore + if parsed_url.scheme and parsed_url.scheme.lower() not in ["http", "https", "file"]: # type: ignore + return "%s%s%s" % (prefix, text, suffix) + href = urlunparse(parsed_url._replace(path=quote(unquote(parsed_url.path)))) # type: ignore + except ValueError: # It's not clear if this ever gets thrown + return "%s%s%s" % (prefix, text, suffix) + + # For the replacement see #29: text nodes underscores are escaped + if ( + self.options["autolinks"] + and text.replace(r"\_", "_") == href + and not title + and not self.options["default_title"] + ): + # Shortcut syntax + return "<%s>" % href + if self.options["default_title"] and not title: + title = href + title_part = ' "%s"' % title.replace('"', r"\"") if title else "" + return ( + "%s[%s](%s%s)%s" % (prefix, text, href, title_part, suffix) + if href + else text + ) + + def convert_img(self, el: Any, text: str, convert_as_inline: bool) -> str: + """Same as usual converter, but removes data URIs""" + + alt = el.attrs.get("alt", None) or "" + src = el.attrs.get("src", None) or "" + title = el.attrs.get("title", None) or "" + title_part = ' "%s"' % title.replace('"', r"\"") if title else "" + if ( + convert_as_inline + and el.parent.name not in self.options["keep_inline_images_in"] + ): + return alt + + # Remove dataURIs + if src.startswith("data:"): + src = src.split(",")[0] + "..." + + return "![%s](%s%s)" % (alt, src, title_part) + + def convert_soup(self, soup: Any) -> str: + return super().convert_soup(soup) # type: ignore diff --git a/packages/markitdown/src/markitdown/converters/_media_converter.py b/packages/markitdown/src/markitdown/converters/_media_converter.py new file mode 100644 index 0000000..07d2bde --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_media_converter.py @@ -0,0 +1,36 @@ +import subprocess +import shutil +import json +from warnings import warn + +from ._base import DocumentConverter + + +class MediaConverter(DocumentConverter): + """ + Abstract class for multi-modal media (e.g., images and audio) + """ + + def _get_metadata(self, local_path, exiftool_path=None): + if not exiftool_path: + which_exiftool = shutil.which("exiftool") + if which_exiftool: + warn( + f"""Implicit discovery of 'exiftool' is disabled. If you would like to continue to use exiftool in MarkItDown, please set the exiftool_path parameter in the MarkItDown consructor. E.g., + + md = MarkItDown(exiftool_path="{which_exiftool}") + +This warning will be removed in future releases. +""", + DeprecationWarning, + ) + + return None + else: + try: + result = subprocess.run( + [exiftool_path, "-json", local_path], capture_output=True, text=True + ).stdout + return json.loads(result)[0] + except Exception: + return None diff --git a/packages/markitdown/src/markitdown/converters/_mp3_converter.py b/packages/markitdown/src/markitdown/converters/_mp3_converter.py new file mode 100644 index 0000000..6b2786b --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_mp3_converter.py @@ -0,0 +1,84 @@ +import tempfile +from typing import Union +from ._base import DocumentConverterResult +from ._wav_converter import WavConverter +from warnings import resetwarnings, catch_warnings + +# Optional Transcription support +IS_AUDIO_TRANSCRIPTION_CAPABLE = False +try: + # Using warnings' catch_warnings to catch + # pydub's warning of ffmpeg or avconv missing + with catch_warnings(record=True) as w: + import pydub + + if w: + raise ModuleNotFoundError + import speech_recognition as sr + + IS_AUDIO_TRANSCRIPTION_CAPABLE = True +except ModuleNotFoundError: + pass +finally: + resetwarnings() + + +class Mp3Converter(WavConverter): + """ + Converts MP3 files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` AND `pydub` are installed). + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a MP3 + extension = kwargs.get("file_extension", "") + if extension.lower() != ".mp3": + return None + + md_content = "" + + # Add metadata + metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) + if metadata: + for f in [ + "Title", + "Artist", + "Author", + "Band", + "Album", + "Genre", + "Track", + "DateTimeOriginal", + "CreateDate", + "Duration", + ]: + if f in metadata: + md_content += f"{f}: {metadata[f]}\n" + + # Transcribe + if IS_AUDIO_TRANSCRIPTION_CAPABLE: + handle, temp_path = tempfile.mkstemp(suffix=".wav") + os.close(handle) + try: + sound = pydub.AudioSegment.from_mp3(local_path) + sound.export(temp_path, format="wav") + + _args = dict() + _args.update(kwargs) + _args["file_extension"] = ".wav" + + try: + transcript = super()._transcribe_audio(temp_path).strip() + md_content += "\n\n### Audio Transcript:\n" + ( + "[No speech detected]" if transcript == "" else transcript + ) + except Exception: + md_content += "\n\n### Audio Transcript:\nError. Could not transcribe this audio." + + finally: + os.unlink(temp_path) + + # Return the result + return DocumentConverterResult( + title=None, + text_content=md_content.strip(), + ) diff --git a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py new file mode 100644 index 0000000..e83001c --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py @@ -0,0 +1,76 @@ +import olefile +from typing import Any, Union +from ._base import DocumentConverter, DocumentConverterResult + + +class OutlookMsgConverter(DocumentConverter): + """Converts Outlook .msg files to markdown by extracting email metadata and content. + + Uses the olefile package to parse the .msg file structure and extract: + - Email headers (From, To, Subject) + - Email body content + """ + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not a MSG file + extension = kwargs.get("file_extension", "") + if extension.lower() != ".msg": + return None + + try: + msg = olefile.OleFileIO(local_path) + # Extract email metadata + md_content = "# Email Message\n\n" + + # Get headers + headers = { + "From": self._get_stream_data(msg, "__substg1.0_0C1F001F"), + "To": self._get_stream_data(msg, "__substg1.0_0E04001F"), + "Subject": self._get_stream_data(msg, "__substg1.0_0037001F"), + } + + # Add headers to markdown + for key, value in headers.items(): + if value: + md_content += f"**{key}:** {value}\n" + + md_content += "\n## Content\n\n" + + # Get email body + body = self._get_stream_data(msg, "__substg1.0_1000001F") + if body: + md_content += body + + msg.close() + + return DocumentConverterResult( + title=headers.get("Subject"), text_content=md_content.strip() + ) + + except Exception as e: + raise FileConversionException( + f"Could not convert MSG file '{local_path}': {str(e)}" + ) + + def _get_stream_data( + self, msg: olefile.OleFileIO, stream_path: str + ) -> Union[str, None]: + """Helper to safely extract and decode stream data from the MSG file.""" + try: + if msg.exists(stream_path): + data = msg.openstream(stream_path).read() + # Try UTF-16 first (common for .msg files) + try: + return data.decode("utf-16-le").strip() + except UnicodeDecodeError: + # Fall back to UTF-8 + try: + return data.decode("utf-8").strip() + except UnicodeDecodeError: + # Last resort - ignore errors + return data.decode("utf-8", errors="ignore").strip() + except Exception: + pass + return None diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py new file mode 100644 index 0000000..dcffc62 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -0,0 +1,21 @@ +import pdfminer +import pdfminer.high_level +from typing import Union +from ._base import DocumentConverter, DocumentConverterResult + + +class PdfConverter(DocumentConverter): + """ + Converts PDFs to Markdown. Most style information is ignored, so the results are essentially plain-text. + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a PDF + extension = kwargs.get("file_extension", "") + if extension.lower() != ".pdf": + return None + + return DocumentConverterResult( + title=None, + text_content=pdfminer.high_level.extract_text(local_path), + ) diff --git a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py new file mode 100644 index 0000000..2912d24 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py @@ -0,0 +1,33 @@ +import mimetypes + +from charset_normalizer import from_path +from typing import Any, Union + +from ._base import DocumentConverter, DocumentConverterResult + + +class PlainTextConverter(DocumentConverter): + """Anything with content type text/plain""" + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Guess the content type from any file extension that might be around + content_type, _ = mimetypes.guess_type( + "__placeholder" + kwargs.get("file_extension", "") + ) + + # Only accept text files + if content_type is None: + return None + elif all( + not content_type.lower().startswith(type_prefix) + for type_prefix in ["text/", "application/json"] + ): + return None + + text_content = str(from_path(local_path).best()) + return DocumentConverterResult( + title=None, + text_content=text_content, + ) diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py new file mode 100644 index 0000000..a48880a --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -0,0 +1,180 @@ +import base64 +import pptx +import re +import html + +from typing import Union + +from ._base import DocumentConverterResult, DocumentConverter +from ._html_converter import HtmlConverter + + +class PptxConverter(HtmlConverter): + """ + Converts PPTX files to Markdown. Supports heading, tables and images with alt text. + """ + + def _get_llm_description( + self, llm_client, llm_model, image_blob, content_type, prompt=None + ): + if prompt is None or prompt.strip() == "": + prompt = "Write a detailed alt text for this image with less than 50 words." + + image_base64 = base64.b64encode(image_blob).decode("utf-8") + data_uri = f"data:{content_type};base64,{image_base64}" + + messages = [ + { + "role": "user", + "content": [ + { + "type": "image_url", + "image_url": { + "url": data_uri, + }, + }, + {"type": "text", "text": prompt}, + ], + } + ] + + response = llm_client.chat.completions.create( + model=llm_model, messages=messages + ) + return response.choices[0].message.content + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a PPTX + extension = kwargs.get("file_extension", "") + if extension.lower() != ".pptx": + return None + + md_content = "" + + presentation = pptx.Presentation(local_path) + slide_num = 0 + for slide in presentation.slides: + slide_num += 1 + + md_content += f"\n\n\n" + + title = slide.shapes.title + for shape in slide.shapes: + # Pictures + if self._is_picture(shape): + # https://github.com/scanny/python-pptx/pull/512#issuecomment-1713100069 + + llm_description = None + alt_text = None + + llm_client = kwargs.get("llm_client") + llm_model = kwargs.get("llm_model") + if llm_client is not None and llm_model is not None: + try: + llm_description = self._get_llm_description( + llm_client, + llm_model, + shape.image.blob, + shape.image.content_type, + ) + except Exception: + # Unable to describe with LLM + pass + + if not llm_description: + try: + alt_text = shape._element._nvXxPr.cNvPr.attrib.get( + "descr", "" + ) + except Exception: + # Unable to get alt text + pass + + # A placeholder name + filename = re.sub(r"\W", "", shape.name) + ".jpg" + md_content += ( + "\n![" + + (llm_description or alt_text or shape.name) + + "](" + + filename + + ")\n" + ) + + # Tables + if self._is_table(shape): + html_table = "" + first_row = True + for row in shape.table.rows: + html_table += "" + for cell in row.cells: + if first_row: + html_table += "" + else: + html_table += "" + html_table += "" + first_row = False + html_table += "
" + html.escape(cell.text) + "" + html.escape(cell.text) + "
" + md_content += ( + "\n" + self._convert(html_table).text_content.strip() + "\n" + ) + + # Charts + if shape.has_chart: + md_content += self._convert_chart_to_markdown(shape.chart) + + # Text areas + elif shape.has_text_frame: + if shape == title: + md_content += "# " + shape.text.lstrip() + "\n" + else: + md_content += shape.text + "\n" + + md_content = md_content.strip() + + if slide.has_notes_slide: + md_content += "\n\n### Notes:\n" + notes_frame = slide.notes_slide.notes_text_frame + if notes_frame is not None: + md_content += notes_frame.text + md_content = md_content.strip() + + return DocumentConverterResult( + title=None, + text_content=md_content.strip(), + ) + + def _is_picture(self, shape): + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PICTURE: + return True + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PLACEHOLDER: + if hasattr(shape, "image"): + return True + return False + + def _is_table(self, shape): + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.TABLE: + return True + return False + + def _convert_chart_to_markdown(self, chart): + md = "\n\n### Chart" + if chart.has_title: + md += f": {chart.chart_title.text_frame.text}" + md += "\n\n" + data = [] + category_names = [c.label for c in chart.plots[0].categories] + series_names = [s.name for s in chart.series] + data.append(["Category"] + series_names) + + for idx, category in enumerate(category_names): + row = [category] + for series in chart.series: + row.append(series.values[idx]) + data.append(row) + + markdown_table = [] + for row in data: + markdown_table.append("| " + " | ".join(map(str, row)) + " |") + header = markdown_table[0] + separator = "|" + "|".join(["---"] * len(data[0])) + "|" + return md + "\n".join([header, separator] + markdown_table[1:]) diff --git a/packages/markitdown/src/markitdown/converters/_rss_converter.py b/packages/markitdown/src/markitdown/converters/_rss_converter.py new file mode 100644 index 0000000..eb2f09c --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_rss_converter.py @@ -0,0 +1,143 @@ +from xml.dom import minidom +from typing import Union +from bs4 import BeautifulSoup + +from ._markdownify import _CustomMarkdownify +from ._base import DocumentConverter, DocumentConverterResult + + +class RssConverter(DocumentConverter): + """Convert RSS / Atom type to markdown""" + + def convert( + self, local_path: str, **kwargs + ) -> Union[None, DocumentConverterResult]: + # Bail if not RSS type + extension = kwargs.get("file_extension", "") + if extension.lower() not in [".xml", ".rss", ".atom"]: + return None + try: + doc = minidom.parse(local_path) + except BaseException as _: + return None + result = None + if doc.getElementsByTagName("rss"): + # A RSS feed must have a root element of + result = self._parse_rss_type(doc) + elif doc.getElementsByTagName("feed"): + root = doc.getElementsByTagName("feed")[0] + if root.getElementsByTagName("entry"): + # An Atom feed must have a root element of and at least one + result = self._parse_atom_type(doc) + else: + return None + else: + # not rss or atom + return None + + return result + + def _parse_atom_type( + self, doc: minidom.Document + ) -> Union[None, DocumentConverterResult]: + """Parse the type of an Atom feed. + + Returns None if the feed type is not recognized or something goes wrong. + """ + try: + root = doc.getElementsByTagName("feed")[0] + title = self._get_data_by_tag_name(root, "title") + subtitle = self._get_data_by_tag_name(root, "subtitle") + entries = root.getElementsByTagName("entry") + md_text = f"# {title}\n" + if subtitle: + md_text += f"{subtitle}\n" + for entry in entries: + entry_title = self._get_data_by_tag_name(entry, "title") + entry_summary = self._get_data_by_tag_name(entry, "summary") + entry_updated = self._get_data_by_tag_name(entry, "updated") + entry_content = self._get_data_by_tag_name(entry, "content") + + if entry_title: + md_text += f"\n## {entry_title}\n" + if entry_updated: + md_text += f"Updated on: {entry_updated}\n" + if entry_summary: + md_text += self._parse_content(entry_summary) + if entry_content: + md_text += self._parse_content(entry_content) + + return DocumentConverterResult( + title=title, + text_content=md_text, + ) + except BaseException as _: + return None + + def _parse_rss_type( + self, doc: minidom.Document + ) -> Union[None, DocumentConverterResult]: + """Parse the type of an RSS feed. + + Returns None if the feed type is not recognized or something goes wrong. + """ + try: + root = doc.getElementsByTagName("rss")[0] + channel = root.getElementsByTagName("channel") + if not channel: + return None + channel = channel[0] + channel_title = self._get_data_by_tag_name(channel, "title") + channel_description = self._get_data_by_tag_name(channel, "description") + items = channel.getElementsByTagName("item") + if channel_title: + md_text = f"# {channel_title}\n" + if channel_description: + md_text += f"{channel_description}\n" + if not items: + items = [] + for item in items: + title = self._get_data_by_tag_name(item, "title") + description = self._get_data_by_tag_name(item, "description") + pubDate = self._get_data_by_tag_name(item, "pubDate") + content = self._get_data_by_tag_name(item, "content:encoded") + + if title: + md_text += f"\n## {title}\n" + if pubDate: + md_text += f"Published on: {pubDate}\n" + if description: + md_text += self._parse_content(description) + if content: + md_text += self._parse_content(content) + + return DocumentConverterResult( + title=channel_title, + text_content=md_text, + ) + except BaseException as _: + print(traceback.format_exc()) + return None + + def _parse_content(self, content: str) -> str: + """Parse the content of an RSS feed item""" + try: + # using bs4 because many RSS feeds have HTML-styled content + soup = BeautifulSoup(content, "html.parser") + return _CustomMarkdownify().convert_soup(soup) + except BaseException as _: + return content + + def _get_data_by_tag_name( + self, element: minidom.Element, tag_name: str + ) -> Union[str, None]: + """Get data from first child element with the given tag name. + Returns None when no such element is found. + """ + nodes = element.getElementsByTagName(tag_name) + if not nodes: + return None + fc = nodes[0].firstChild + if fc: + return fc.data + return None diff --git a/packages/markitdown/src/markitdown/converters/_wav_converter.py b/packages/markitdown/src/markitdown/converters/_wav_converter.py new file mode 100644 index 0000000..6fc8932 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_wav_converter.py @@ -0,0 +1,67 @@ +from typing import Union +from ._base import DocumentConverterResult +from ._media_converter import MediaConverter + +# Optional Transcription support +IS_AUDIO_TRANSCRIPTION_CAPABLE = False +try: + import speech_recognition as sr + + IS_AUDIO_TRANSCRIPTION_CAPABLE = True +except ModuleNotFoundError: + pass + + +class WavConverter(MediaConverter): + """ + Converts WAV files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` is installed). + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a WAV + extension = kwargs.get("file_extension", "") + if extension.lower() != ".wav": + return None + + md_content = "" + + # Add metadata + metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) + if metadata: + for f in [ + "Title", + "Artist", + "Author", + "Band", + "Album", + "Genre", + "Track", + "DateTimeOriginal", + "CreateDate", + "Duration", + ]: + if f in metadata: + md_content += f"{f}: {metadata[f]}\n" + + # Transcribe + if IS_AUDIO_TRANSCRIPTION_CAPABLE: + try: + transcript = self._transcribe_audio(local_path) + md_content += "\n\n### Audio Transcript:\n" + ( + "[No speech detected]" if transcript == "" else transcript + ) + except Exception: + md_content += ( + "\n\n### Audio Transcript:\nError. Could not transcribe this audio." + ) + + return DocumentConverterResult( + title=None, + text_content=md_content.strip(), + ) + + def _transcribe_audio(self, local_path) -> str: + recognizer = sr.Recognizer() + with sr.AudioFile(local_path) as source: + audio = recognizer.record(source) + return recognizer.recognize_google(audio).strip() diff --git a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py new file mode 100644 index 0000000..4097ef0 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py @@ -0,0 +1,56 @@ +import re + +from typing import Any, Union +from bs4 import BeautifulSoup + +from ._base import DocumentConverter, DocumentConverterResult +from ._markdownify import _CustomMarkdownify + + +class WikipediaConverter(DocumentConverter): + """Handle Wikipedia pages separately, focusing only on the main document content.""" + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not Wikipedia + extension = kwargs.get("file_extension", "") + if extension.lower() not in [".html", ".htm"]: + return None + url = kwargs.get("url", "") + if not re.search(r"^https?:\/\/[a-zA-Z]{2,3}\.wikipedia.org\/", url): + return None + + # Parse the file + soup = None + with open(local_path, "rt", encoding="utf-8") as fh: + soup = BeautifulSoup(fh.read(), "html.parser") + + # Remove javascript and style blocks + for script in soup(["script", "style"]): + script.extract() + + # Print only the main content + body_elm = soup.find("div", {"id": "mw-content-text"}) + title_elm = soup.find("span", {"class": "mw-page-title-main"}) + + webpage_text = "" + main_title = None if soup.title is None else soup.title.string + + if body_elm: + # What's the title + if title_elm and len(title_elm) > 0: + main_title = title_elm.string # type: ignore + assert isinstance(main_title, str) + + # Convert the page + webpage_text = f"# {main_title}\n\n" + _CustomMarkdownify().convert_soup( + body_elm + ) + else: + webpage_text = _CustomMarkdownify().convert_soup(soup) + + return DocumentConverterResult( + title=main_title, + text_content=webpage_text, + ) diff --git a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py new file mode 100644 index 0000000..683349c --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py @@ -0,0 +1,54 @@ +from typing import Union + +import pandas as pd + +from ._base import DocumentConverterResult +from ._html_converter import HtmlConverter + + +class XlsxConverter(HtmlConverter): + """ + Converts XLSX files to Markdown, with each sheet presented as a separate Markdown table. + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a XLSX + extension = kwargs.get("file_extension", "") + if extension.lower() != ".xlsx": + return None + + sheets = pd.read_excel(local_path, sheet_name=None, engine="openpyxl") + md_content = "" + for s in sheets: + md_content += f"## {s}\n" + html_content = sheets[s].to_html(index=False) + md_content += self._convert(html_content).text_content.strip() + "\n\n" + + return DocumentConverterResult( + title=None, + text_content=md_content.strip(), + ) + + +class XlsConverter(HtmlConverter): + """ + Converts XLS files to Markdown, with each sheet presented as a separate Markdown table. + """ + + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: + # Bail if not a XLS + extension = kwargs.get("file_extension", "") + if extension.lower() != ".xls": + return None + + sheets = pd.read_excel(local_path, sheet_name=None, engine="xlrd") + md_content = "" + for s in sheets: + md_content += f"## {s}\n" + html_content = sheets[s].to_html(index=False) + md_content += self._convert(html_content).text_content.strip() + "\n\n" + + return DocumentConverterResult( + title=None, + text_content=md_content.strip(), + ) diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py new file mode 100644 index 0000000..fe198e8 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -0,0 +1,148 @@ +import re + +from typing import Any, Union, Dict, List +from urllib.parse import parse_qs, urlparse +from bs4 import BeautifulSoup + +from ._base import DocumentConverter, DocumentConverterResult + + +# Optional YouTube transcription support +try: + from youtube_transcript_api import YouTubeTranscriptApi + + IS_YOUTUBE_TRANSCRIPT_CAPABLE = True +except ModuleNotFoundError: + pass + + +class YouTubeConverter(DocumentConverter): + """Handle YouTube specially, focusing on the video title, description, and transcript.""" + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not YouTube + extension = kwargs.get("file_extension", "") + if extension.lower() not in [".html", ".htm"]: + return None + url = kwargs.get("url", "") + if not url.startswith("https://www.youtube.com/watch?"): + return None + + # Parse the file + soup = None + with open(local_path, "rt", encoding="utf-8") as fh: + soup = BeautifulSoup(fh.read(), "html.parser") + + # Read the meta tags + assert soup.title is not None and soup.title.string is not None + metadata: Dict[str, str] = {"title": soup.title.string} + for meta in soup(["meta"]): + for a in meta.attrs: + if a in ["itemprop", "property", "name"]: + metadata[meta[a]] = meta.get("content", "") + break + + # We can also try to read the full description. This is more prone to breaking, since it reaches into the page implementation + try: + for script in soup(["script"]): + content = script.text + if "ytInitialData" in content: + lines = re.split(r"\r?\n", content) + obj_start = lines[0].find("{") + obj_end = lines[0].rfind("}") + if obj_start >= 0 and obj_end >= 0: + data = json.loads(lines[0][obj_start : obj_end + 1]) + attrdesc = self._findKey(data, "attributedDescriptionBodyText") # type: ignore + if attrdesc: + metadata["description"] = str(attrdesc["content"]) + break + except Exception: + pass + + # Start preparing the page + webpage_text = "# YouTube\n" + + title = self._get(metadata, ["title", "og:title", "name"]) # type: ignore + assert isinstance(title, str) + + if title: + webpage_text += f"\n## {title}\n" + + stats = "" + views = self._get(metadata, ["interactionCount"]) # type: ignore + if views: + stats += f"- **Views:** {views}\n" + + keywords = self._get(metadata, ["keywords"]) # type: ignore + if keywords: + stats += f"- **Keywords:** {keywords}\n" + + runtime = self._get(metadata, ["duration"]) # type: ignore + if runtime: + stats += f"- **Runtime:** {runtime}\n" + + if len(stats) > 0: + webpage_text += f"\n### Video Metadata\n{stats}\n" + + description = self._get(metadata, ["description", "og:description"]) # type: ignore + if description: + webpage_text += f"\n### Description\n{description}\n" + + if IS_YOUTUBE_TRANSCRIPT_CAPABLE: + transcript_text = "" + parsed_url = urlparse(url) # type: ignore + params = parse_qs(parsed_url.query) # type: ignore + if "v" in params: + assert isinstance(params["v"][0], str) + video_id = str(params["v"][0]) + try: + youtube_transcript_languages = kwargs.get( + "youtube_transcript_languages", ("en",) + ) + # Must be a single transcript. + transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=youtube_transcript_languages) # type: ignore + transcript_text = " ".join([part["text"] for part in transcript]) # type: ignore + # Alternative formatting: + # formatter = TextFormatter() + # formatter.format_transcript(transcript) + except Exception: + pass + if transcript_text: + webpage_text += f"\n### Transcript\n{transcript_text}\n" + + title = title if title else soup.title.string + assert isinstance(title, str) + + return DocumentConverterResult( + title=title, + text_content=webpage_text, + ) + + def _get( + self, + metadata: Dict[str, str], + keys: List[str], + default: Union[str, None] = None, + ) -> Union[str, None]: + for k in keys: + if k in metadata: + return metadata[k] + return default + + def _findKey(self, json: Any, key: str) -> Union[str, None]: # TODO: Fix json type + if isinstance(json, list): + for elm in json: + ret = self._findKey(elm, key) + if ret is not None: + return ret + elif isinstance(json, dict): + for k in json: + if k == key: + return json[k] + else: + ret = self._findKey(json[k], key) + if ret is not None: + return ret + return None diff --git a/packages/markitdown/src/markitdown/converters/_zip_converter.py b/packages/markitdown/src/markitdown/converters/_zip_converter.py new file mode 100644 index 0000000..918c357 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_zip_converter.py @@ -0,0 +1,135 @@ +import os +import zipfile +import shutil +from typing import Any, Union + +from ._base import DocumentConverter, DocumentConverterResult + + +class ZipConverter(DocumentConverter): + """Converts ZIP files to markdown by extracting and converting all contained files. + + The converter extracts the ZIP contents to a temporary directory, processes each file + using appropriate converters based on file extensions, and then combines the results + into a single markdown document. The temporary directory is cleaned up after processing. + + Example output format: + ```markdown + Content from the zip file `example.zip`: + + ## File: docs/readme.txt + + This is the content of readme.txt + Multiple lines are preserved + + ## File: images/example.jpg + + ImageSize: 1920x1080 + DateTimeOriginal: 2024-02-15 14:30:00 + Description: A beautiful landscape photo + + ## File: data/report.xlsx + + ## Sheet1 + | Column1 | Column2 | Column3 | + |---------|---------|---------| + | data1 | data2 | data3 | + | data4 | data5 | data6 | + ``` + + Key features: + - Maintains original file structure in headings + - Processes nested files recursively + - Uses appropriate converters for each file type + - Preserves formatting of converted content + - Cleans up temporary files after processing + """ + + def convert( + self, local_path: str, **kwargs: Any + ) -> Union[None, DocumentConverterResult]: + # Bail if not a ZIP + extension = kwargs.get("file_extension", "") + if extension.lower() != ".zip": + return None + + # Get parent converters list if available + parent_converters = kwargs.get("_parent_converters", []) + if not parent_converters: + return DocumentConverterResult( + title=None, + text_content=f"[ERROR] No converters available to process zip contents from: {local_path}", + ) + + extracted_zip_folder_name = ( + f"extracted_{os.path.basename(local_path).replace('.zip', '_zip')}" + ) + extraction_dir = os.path.normpath( + os.path.join(os.path.dirname(local_path), extracted_zip_folder_name) + ) + md_content = f"Content from the zip file `{os.path.basename(local_path)}`:\n\n" + + try: + # Extract the zip file safely + with zipfile.ZipFile(local_path, "r") as zipObj: + # Safeguard against path traversal + for member in zipObj.namelist(): + member_path = os.path.normpath(os.path.join(extraction_dir, member)) + if ( + not os.path.commonprefix([extraction_dir, member_path]) + == extraction_dir + ): + raise ValueError( + f"Path traversal detected in zip file: {member}" + ) + + # Extract all files safely + zipObj.extractall(path=extraction_dir) + + # Process each extracted file + for root, dirs, files in os.walk(extraction_dir): + for name in files: + file_path = os.path.join(root, name) + relative_path = os.path.relpath(file_path, extraction_dir) + + # Get file extension + _, file_extension = os.path.splitext(name) + + # Update kwargs for the file + file_kwargs = kwargs.copy() + file_kwargs["file_extension"] = file_extension + file_kwargs["_parent_converters"] = parent_converters + + # Try converting the file using available converters + for converter in parent_converters: + # Skip the zip converter to avoid infinite recursion + if isinstance(converter, ZipConverter): + continue + + result = converter.convert(file_path, **file_kwargs) + if result is not None: + md_content += f"\n## File: {relative_path}\n\n" + md_content += result.text_content + "\n\n" + break + + # Clean up extracted files if specified + if kwargs.get("cleanup_extracted", True): + shutil.rmtree(extraction_dir) + + return DocumentConverterResult(title=None, text_content=md_content.strip()) + + except zipfile.BadZipFile: + return DocumentConverterResult( + title=None, + text_content=f"[ERROR] Invalid or corrupted zip file: {local_path}", + ) + except ValueError as ve: + return DocumentConverterResult( + title=None, + text_content=f"[ERROR] Security error in zip file {local_path}: {str(ve)}", + ) + except Exception as e: + return DocumentConverterResult( + title=None, + text_content=f"[ERROR] Failed to process zip file {local_path}: {str(e)}", + ) diff --git a/packages/markitdown/src/markitdown/py.typed b/packages/markitdown/src/markitdown/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/packages/markitdown/tests/__init__.py b/packages/markitdown/tests/__init__.py new file mode 100644 index 0000000..44af7d7 --- /dev/null +++ b/packages/markitdown/tests/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT diff --git a/tests/test_files/test.docx b/packages/markitdown/tests/test_files/test.docx similarity index 100% rename from tests/test_files/test.docx rename to packages/markitdown/tests/test_files/test.docx diff --git a/tests/test_files/test.jpg b/packages/markitdown/tests/test_files/test.jpg similarity index 100% rename from tests/test_files/test.jpg rename to packages/markitdown/tests/test_files/test.jpg diff --git a/tests/test_files/test.json b/packages/markitdown/tests/test_files/test.json similarity index 100% rename from tests/test_files/test.json rename to packages/markitdown/tests/test_files/test.json diff --git a/tests/test_files/test.pptx b/packages/markitdown/tests/test_files/test.pptx similarity index 100% rename from tests/test_files/test.pptx rename to packages/markitdown/tests/test_files/test.pptx diff --git a/tests/test_files/test.xls b/packages/markitdown/tests/test_files/test.xls similarity index 100% rename from tests/test_files/test.xls rename to packages/markitdown/tests/test_files/test.xls diff --git a/tests/test_files/test.xlsx b/packages/markitdown/tests/test_files/test.xlsx similarity index 100% rename from tests/test_files/test.xlsx rename to packages/markitdown/tests/test_files/test.xlsx diff --git a/tests/test_files/test_blog.html b/packages/markitdown/tests/test_files/test_blog.html similarity index 100% rename from tests/test_files/test_blog.html rename to packages/markitdown/tests/test_files/test_blog.html diff --git a/tests/test_files/test_files.zip b/packages/markitdown/tests/test_files/test_files.zip similarity index 100% rename from tests/test_files/test_files.zip rename to packages/markitdown/tests/test_files/test_files.zip diff --git a/tests/test_files/test_llm.jpg b/packages/markitdown/tests/test_files/test_llm.jpg similarity index 100% rename from tests/test_files/test_llm.jpg rename to packages/markitdown/tests/test_files/test_llm.jpg diff --git a/tests/test_files/test_mskanji.csv b/packages/markitdown/tests/test_files/test_mskanji.csv similarity index 100% rename from tests/test_files/test_mskanji.csv rename to packages/markitdown/tests/test_files/test_mskanji.csv diff --git a/tests/test_files/test_notebook.ipynb b/packages/markitdown/tests/test_files/test_notebook.ipynb similarity index 100% rename from tests/test_files/test_notebook.ipynb rename to packages/markitdown/tests/test_files/test_notebook.ipynb diff --git a/tests/test_files/test_outlook_msg.msg b/packages/markitdown/tests/test_files/test_outlook_msg.msg similarity index 100% rename from tests/test_files/test_outlook_msg.msg rename to packages/markitdown/tests/test_files/test_outlook_msg.msg diff --git a/tests/test_files/test_rss.xml b/packages/markitdown/tests/test_files/test_rss.xml similarity index 100% rename from tests/test_files/test_rss.xml rename to packages/markitdown/tests/test_files/test_rss.xml diff --git a/tests/test_files/test_serp.html b/packages/markitdown/tests/test_files/test_serp.html similarity index 100% rename from tests/test_files/test_serp.html rename to packages/markitdown/tests/test_files/test_serp.html diff --git a/tests/test_files/test_wikipedia.html b/packages/markitdown/tests/test_files/test_wikipedia.html similarity index 100% rename from tests/test_files/test_wikipedia.html rename to packages/markitdown/tests/test_files/test_wikipedia.html diff --git a/tests/test_files/test_with_comment.docx b/packages/markitdown/tests/test_files/test_with_comment.docx similarity index 100% rename from tests/test_files/test_with_comment.docx rename to packages/markitdown/tests/test_files/test_with_comment.docx diff --git a/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py similarity index 92% rename from tests/test_markitdown.py rename to packages/markitdown/tests/test_markitdown.py index 689d6f3..be71722 100644 --- a/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -306,40 +306,6 @@ def test_markitdown_exiftool() -> None: assert target in result.text_content -def test_markitdown_deprecation() -> None: - try: - with catch_warnings(record=True) as w: - test_client = object() - markitdown = MarkItDown(mlm_client=test_client) - assert len(w) == 1 - assert w[0].category is DeprecationWarning - assert markitdown._llm_client == test_client - finally: - resetwarnings() - - try: - with catch_warnings(record=True) as w: - markitdown = MarkItDown(mlm_model="gpt-4o") - assert len(w) == 1 - assert w[0].category is DeprecationWarning - assert markitdown._llm_model == "gpt-4o" - finally: - resetwarnings() - - try: - test_client = object() - markitdown = MarkItDown(mlm_client=test_client, llm_client=test_client) - assert False - except ValueError: - pass - - try: - markitdown = MarkItDown(mlm_model="gpt-4o", llm_model="gpt-4o") - assert False - except ValueError: - pass - - @pytest.mark.skipif( skip_llm, reason="do not run llm tests without a key", diff --git a/src/markitdown/__init__.py b/src/markitdown/__init__.py deleted file mode 100644 index 482f428..0000000 --- a/src/markitdown/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2024-present Adam Fourney -# -# SPDX-License-Identifier: MIT - -from ._markitdown import MarkItDown, FileConversionException, UnsupportedFormatException - -__all__ = [ - "MarkItDown", - "FileConversionException", - "UnsupportedFormatException", -] diff --git a/src/markitdown/_markitdown.py b/src/markitdown/_markitdown.py deleted file mode 100644 index e68b099..0000000 --- a/src/markitdown/_markitdown.py +++ /dev/null @@ -1,1801 +0,0 @@ -# type: ignore -import base64 -import binascii -import copy -import html -import json -import mimetypes -import os -import re -import shutil -import subprocess -import sys -import tempfile -import traceback -import zipfile -from xml.dom import minidom -from typing import Any, Dict, List, Optional, Union -from pathlib import Path -from urllib.parse import parse_qs, quote, unquote, urlparse, urlunparse -from warnings import warn, resetwarnings, catch_warnings - -import mammoth -import markdownify -import olefile -import pandas as pd -import pdfminer -import pdfminer.high_level -import pptx - -# File-format detection -import puremagic -import requests -from bs4 import BeautifulSoup -from charset_normalizer import from_path - -# Azure imports -from azure.ai.documentintelligence import DocumentIntelligenceClient -from azure.ai.documentintelligence.models import ( - AnalyzeDocumentRequest, - AnalyzeResult, - DocumentAnalysisFeature, -) -from azure.identity import DefaultAzureCredential - -# TODO: currently, there is a bug in the document intelligence SDK with importing the "ContentFormat" enum. -# This constant is a temporary fix until the bug is resolved. -CONTENT_FORMAT = "markdown" - -# Override mimetype for csv to fix issue on windows -mimetypes.add_type("text/csv", ".csv") - -# Optional Transcription support -IS_AUDIO_TRANSCRIPTION_CAPABLE = False -try: - # Using warnings' catch_warnings to catch - # pydub's warning of ffmpeg or avconv missing - with catch_warnings(record=True) as w: - import pydub - - if w: - raise ModuleNotFoundError - import speech_recognition as sr - - IS_AUDIO_TRANSCRIPTION_CAPABLE = True -except ModuleNotFoundError: - pass -finally: - resetwarnings() - -# Optional YouTube transcription support -try: - from youtube_transcript_api import YouTubeTranscriptApi - - IS_YOUTUBE_TRANSCRIPT_CAPABLE = True -except ModuleNotFoundError: - pass - - -class _CustomMarkdownify(markdownify.MarkdownConverter): - """ - A custom version of markdownify's MarkdownConverter. Changes include: - - - Altering the default heading style to use '#', '##', etc. - - Removing javascript hyperlinks. - - Truncating images with large data:uri sources. - - Ensuring URIs are properly escaped, and do not conflict with Markdown syntax - """ - - def __init__(self, **options: Any): - options["heading_style"] = options.get("heading_style", markdownify.ATX) - # Explicitly cast options to the expected type if necessary - super().__init__(**options) - - def convert_hn(self, n: int, el: Any, text: str, convert_as_inline: bool) -> str: - """Same as usual, but be sure to start with a new line""" - if not convert_as_inline: - if not re.search(r"^\n", text): - return "\n" + super().convert_hn(n, el, text, convert_as_inline) # type: ignore - - return super().convert_hn(n, el, text, convert_as_inline) # type: ignore - - def convert_a(self, el: Any, text: str, convert_as_inline: bool): - """Same as usual converter, but removes Javascript links and escapes URIs.""" - prefix, suffix, text = markdownify.chomp(text) # type: ignore - if not text: - return "" - href = el.get("href") - title = el.get("title") - - # Escape URIs and skip non-http or file schemes - if href: - try: - parsed_url = urlparse(href) # type: ignore - if parsed_url.scheme and parsed_url.scheme.lower() not in ["http", "https", "file"]: # type: ignore - return "%s%s%s" % (prefix, text, suffix) - href = urlunparse(parsed_url._replace(path=quote(unquote(parsed_url.path)))) # type: ignore - except ValueError: # It's not clear if this ever gets thrown - return "%s%s%s" % (prefix, text, suffix) - - # For the replacement see #29: text nodes underscores are escaped - if ( - self.options["autolinks"] - and text.replace(r"\_", "_") == href - and not title - and not self.options["default_title"] - ): - # Shortcut syntax - return "<%s>" % href - if self.options["default_title"] and not title: - title = href - title_part = ' "%s"' % title.replace('"', r"\"") if title else "" - return ( - "%s[%s](%s%s)%s" % (prefix, text, href, title_part, suffix) - if href - else text - ) - - def convert_img(self, el: Any, text: str, convert_as_inline: bool) -> str: - """Same as usual converter, but removes data URIs""" - - alt = el.attrs.get("alt", None) or "" - src = el.attrs.get("src", None) or "" - title = el.attrs.get("title", None) or "" - title_part = ' "%s"' % title.replace('"', r"\"") if title else "" - if ( - convert_as_inline - and el.parent.name not in self.options["keep_inline_images_in"] - ): - return alt - - # Remove dataURIs - if src.startswith("data:"): - src = src.split(",")[0] + "..." - - return "![%s](%s%s)" % (alt, src, title_part) - - def convert_soup(self, soup: Any) -> str: - return super().convert_soup(soup) # type: ignore - - -class DocumentConverterResult: - """The result of converting a document to text.""" - - def __init__(self, title: Union[str, None] = None, text_content: str = ""): - self.title: Union[str, None] = title - self.text_content: str = text_content - - -class DocumentConverter: - """Abstract superclass of all DocumentConverters.""" - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - raise NotImplementedError() - - -class PlainTextConverter(DocumentConverter): - """Anything with content type text/plain""" - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Guess the content type from any file extension that might be around - content_type, _ = mimetypes.guess_type( - "__placeholder" + kwargs.get("file_extension", "") - ) - - # Only accept text files - if content_type is None: - return None - elif all( - not content_type.lower().startswith(type_prefix) - for type_prefix in ["text/", "application/json"] - ): - return None - - text_content = str(from_path(local_path).best()) - return DocumentConverterResult( - title=None, - text_content=text_content, - ) - - -class HtmlConverter(DocumentConverter): - """Anything with content type text/html""" - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not html - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - - result = None - with open(local_path, "rt", encoding="utf-8") as fh: - result = self._convert(fh.read()) - - return result - - def _convert(self, html_content: str) -> Union[None, DocumentConverterResult]: - """Helper function that converts an HTML string.""" - - # Parse the string - soup = BeautifulSoup(html_content, "html.parser") - - # Remove javascript and style blocks - for script in soup(["script", "style"]): - script.extract() - - # Print only the main content - body_elm = soup.find("body") - webpage_text = "" - if body_elm: - webpage_text = _CustomMarkdownify().convert_soup(body_elm) - else: - webpage_text = _CustomMarkdownify().convert_soup(soup) - - assert isinstance(webpage_text, str) - - # remove leading and trailing \n - webpage_text = webpage_text.strip() - - return DocumentConverterResult( - title=None if soup.title is None else soup.title.string, - text_content=webpage_text, - ) - - -class RSSConverter(DocumentConverter): - """Convert RSS / Atom type to markdown""" - - def convert( - self, local_path: str, **kwargs - ) -> Union[None, DocumentConverterResult]: - # Bail if not RSS type - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".xml", ".rss", ".atom"]: - return None - try: - doc = minidom.parse(local_path) - except BaseException as _: - return None - result = None - if doc.getElementsByTagName("rss"): - # A RSS feed must have a root element of - result = self._parse_rss_type(doc) - elif doc.getElementsByTagName("feed"): - root = doc.getElementsByTagName("feed")[0] - if root.getElementsByTagName("entry"): - # An Atom feed must have a root element of and at least one - result = self._parse_atom_type(doc) - else: - return None - else: - # not rss or atom - return None - - return result - - def _parse_atom_type( - self, doc: minidom.Document - ) -> Union[None, DocumentConverterResult]: - """Parse the type of an Atom feed. - - Returns None if the feed type is not recognized or something goes wrong. - """ - try: - root = doc.getElementsByTagName("feed")[0] - title = self._get_data_by_tag_name(root, "title") - subtitle = self._get_data_by_tag_name(root, "subtitle") - entries = root.getElementsByTagName("entry") - md_text = f"# {title}\n" - if subtitle: - md_text += f"{subtitle}\n" - for entry in entries: - entry_title = self._get_data_by_tag_name(entry, "title") - entry_summary = self._get_data_by_tag_name(entry, "summary") - entry_updated = self._get_data_by_tag_name(entry, "updated") - entry_content = self._get_data_by_tag_name(entry, "content") - - if entry_title: - md_text += f"\n## {entry_title}\n" - if entry_updated: - md_text += f"Updated on: {entry_updated}\n" - if entry_summary: - md_text += self._parse_content(entry_summary) - if entry_content: - md_text += self._parse_content(entry_content) - - return DocumentConverterResult( - title=title, - text_content=md_text, - ) - except BaseException as _: - return None - - def _parse_rss_type( - self, doc: minidom.Document - ) -> Union[None, DocumentConverterResult]: - """Parse the type of an RSS feed. - - Returns None if the feed type is not recognized or something goes wrong. - """ - try: - root = doc.getElementsByTagName("rss")[0] - channel = root.getElementsByTagName("channel") - if not channel: - return None - channel = channel[0] - channel_title = self._get_data_by_tag_name(channel, "title") - channel_description = self._get_data_by_tag_name(channel, "description") - items = channel.getElementsByTagName("item") - if channel_title: - md_text = f"# {channel_title}\n" - if channel_description: - md_text += f"{channel_description}\n" - if not items: - items = [] - for item in items: - title = self._get_data_by_tag_name(item, "title") - description = self._get_data_by_tag_name(item, "description") - pubDate = self._get_data_by_tag_name(item, "pubDate") - content = self._get_data_by_tag_name(item, "content:encoded") - - if title: - md_text += f"\n## {title}\n" - if pubDate: - md_text += f"Published on: {pubDate}\n" - if description: - md_text += self._parse_content(description) - if content: - md_text += self._parse_content(content) - - return DocumentConverterResult( - title=channel_title, - text_content=md_text, - ) - except BaseException as _: - print(traceback.format_exc()) - return None - - def _parse_content(self, content: str) -> str: - """Parse the content of an RSS feed item""" - try: - # using bs4 because many RSS feeds have HTML-styled content - soup = BeautifulSoup(content, "html.parser") - return _CustomMarkdownify().convert_soup(soup) - except BaseException as _: - return content - - def _get_data_by_tag_name( - self, element: minidom.Element, tag_name: str - ) -> Union[str, None]: - """Get data from first child element with the given tag name. - Returns None when no such element is found. - """ - nodes = element.getElementsByTagName(tag_name) - if not nodes: - return None - fc = nodes[0].firstChild - if fc: - return fc.data - return None - - -class WikipediaConverter(DocumentConverter): - """Handle Wikipedia pages separately, focusing only on the main document content.""" - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not Wikipedia - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - url = kwargs.get("url", "") - if not re.search(r"^https?:\/\/[a-zA-Z]{2,3}\.wikipedia.org\/", url): - return None - - # Parse the file - soup = None - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") - - # Remove javascript and style blocks - for script in soup(["script", "style"]): - script.extract() - - # Print only the main content - body_elm = soup.find("div", {"id": "mw-content-text"}) - title_elm = soup.find("span", {"class": "mw-page-title-main"}) - - webpage_text = "" - main_title = None if soup.title is None else soup.title.string - - if body_elm: - # What's the title - if title_elm and len(title_elm) > 0: - main_title = title_elm.string # type: ignore - assert isinstance(main_title, str) - - # Convert the page - webpage_text = f"# {main_title}\n\n" + _CustomMarkdownify().convert_soup( - body_elm - ) - else: - webpage_text = _CustomMarkdownify().convert_soup(soup) - - return DocumentConverterResult( - title=main_title, - text_content=webpage_text, - ) - - -class YouTubeConverter(DocumentConverter): - """Handle YouTube specially, focusing on the video title, description, and transcript.""" - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not YouTube - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - url = kwargs.get("url", "") - if not url.startswith("https://www.youtube.com/watch?"): - return None - - # Parse the file - soup = None - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") - - # Read the meta tags - assert soup.title is not None and soup.title.string is not None - metadata: Dict[str, str] = {"title": soup.title.string} - for meta in soup(["meta"]): - for a in meta.attrs: - if a in ["itemprop", "property", "name"]: - metadata[meta[a]] = meta.get("content", "") - break - - # We can also try to read the full description. This is more prone to breaking, since it reaches into the page implementation - try: - for script in soup(["script"]): - content = script.text - if "ytInitialData" in content: - lines = re.split(r"\r?\n", content) - obj_start = lines[0].find("{") - obj_end = lines[0].rfind("}") - if obj_start >= 0 and obj_end >= 0: - data = json.loads(lines[0][obj_start : obj_end + 1]) - attrdesc = self._findKey(data, "attributedDescriptionBodyText") # type: ignore - if attrdesc: - metadata["description"] = str(attrdesc["content"]) - break - except Exception: - pass - - # Start preparing the page - webpage_text = "# YouTube\n" - - title = self._get(metadata, ["title", "og:title", "name"]) # type: ignore - assert isinstance(title, str) - - if title: - webpage_text += f"\n## {title}\n" - - stats = "" - views = self._get(metadata, ["interactionCount"]) # type: ignore - if views: - stats += f"- **Views:** {views}\n" - - keywords = self._get(metadata, ["keywords"]) # type: ignore - if keywords: - stats += f"- **Keywords:** {keywords}\n" - - runtime = self._get(metadata, ["duration"]) # type: ignore - if runtime: - stats += f"- **Runtime:** {runtime}\n" - - if len(stats) > 0: - webpage_text += f"\n### Video Metadata\n{stats}\n" - - description = self._get(metadata, ["description", "og:description"]) # type: ignore - if description: - webpage_text += f"\n### Description\n{description}\n" - - if IS_YOUTUBE_TRANSCRIPT_CAPABLE: - transcript_text = "" - parsed_url = urlparse(url) # type: ignore - params = parse_qs(parsed_url.query) # type: ignore - if "v" in params: - assert isinstance(params["v"][0], str) - video_id = str(params["v"][0]) - try: - youtube_transcript_languages = kwargs.get( - "youtube_transcript_languages", ("en",) - ) - # Must be a single transcript. - transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=youtube_transcript_languages) # type: ignore - transcript_text = " ".join([part["text"] for part in transcript]) # type: ignore - # Alternative formatting: - # formatter = TextFormatter() - # formatter.format_transcript(transcript) - except Exception: - pass - if transcript_text: - webpage_text += f"\n### Transcript\n{transcript_text}\n" - - title = title if title else soup.title.string - assert isinstance(title, str) - - return DocumentConverterResult( - title=title, - text_content=webpage_text, - ) - - def _get( - self, - metadata: Dict[str, str], - keys: List[str], - default: Union[str, None] = None, - ) -> Union[str, None]: - for k in keys: - if k in metadata: - return metadata[k] - return default - - def _findKey(self, json: Any, key: str) -> Union[str, None]: # TODO: Fix json type - if isinstance(json, list): - for elm in json: - ret = self._findKey(elm, key) - if ret is not None: - return ret - elif isinstance(json, dict): - for k in json: - if k == key: - return json[k] - else: - ret = self._findKey(json[k], key) - if ret is not None: - return ret - return None - - -class IpynbConverter(DocumentConverter): - """Converts Jupyter Notebook (.ipynb) files to Markdown.""" - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not ipynb - extension = kwargs.get("file_extension", "") - if extension.lower() != ".ipynb": - return None - - # Parse and convert the notebook - result = None - with open(local_path, "rt", encoding="utf-8") as fh: - notebook_content = json.load(fh) - result = self._convert(notebook_content) - - return result - - def _convert(self, notebook_content: dict) -> Union[None, DocumentConverterResult]: - """Helper function that converts notebook JSON content to Markdown.""" - try: - md_output = [] - title = None - - for cell in notebook_content.get("cells", []): - cell_type = cell.get("cell_type", "") - source_lines = cell.get("source", []) - - if cell_type == "markdown": - md_output.append("".join(source_lines)) - - # Extract the first # heading as title if not already found - if title is None: - for line in source_lines: - if line.startswith("# "): - title = line.lstrip("# ").strip() - break - - elif cell_type == "code": - # Code cells are wrapped in Markdown code blocks - md_output.append(f"```python\n{''.join(source_lines)}\n```") - elif cell_type == "raw": - md_output.append(f"```\n{''.join(source_lines)}\n```") - - md_text = "\n\n".join(md_output) - - # Check for title in notebook metadata - title = notebook_content.get("metadata", {}).get("title", title) - - return DocumentConverterResult( - title=title, - text_content=md_text, - ) - - except Exception as e: - raise FileConversionException( - f"Error converting .ipynb file: {str(e)}" - ) from e - - -class BingSerpConverter(DocumentConverter): - """ - Handle Bing results pages (only the organic search results). - NOTE: It is better to use the Bing API - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a Bing SERP - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - url = kwargs.get("url", "") - if not re.search(r"^https://www\.bing\.com/search\?q=", url): - return None - - # Parse the query parameters - parsed_params = parse_qs(urlparse(url).query) - query = parsed_params.get("q", [""])[0] - - # Parse the file - soup = None - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") - - # Clean up some formatting - for tptt in soup.find_all(class_="tptt"): - if hasattr(tptt, "string") and tptt.string: - tptt.string += " " - for slug in soup.find_all(class_="algoSlug_icon"): - slug.extract() - - # Parse the algorithmic results - _markdownify = _CustomMarkdownify() - results = list() - for result in soup.find_all(class_="b_algo"): - # Rewrite redirect urls - for a in result.find_all("a", href=True): - parsed_href = urlparse(a["href"]) - qs = parse_qs(parsed_href.query) - - # The destination is contained in the u parameter, - # but appears to be base64 encoded, with some prefix - if "u" in qs: - u = ( - qs["u"][0][2:].strip() + "==" - ) # Python 3 doesn't care about extra padding - - try: - # RFC 4648 / Base64URL" variant, which uses "-" and "_" - a["href"] = base64.b64decode(u, altchars="-_").decode("utf-8") - except UnicodeDecodeError: - pass - except binascii.Error: - pass - - # Convert to markdown - md_result = _markdownify.convert_soup(result).strip() - lines = [line.strip() for line in re.split(r"\n+", md_result)] - results.append("\n".join([line for line in lines if len(line) > 0])) - - webpage_text = ( - f"## A Bing search for '{query}' found the following results:\n\n" - + "\n\n".join(results) - ) - - return DocumentConverterResult( - title=None if soup.title is None else soup.title.string, - text_content=webpage_text, - ) - - -class PdfConverter(DocumentConverter): - """ - Converts PDFs to Markdown. Most style information is ignored, so the results are essentially plain-text. - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a PDF - extension = kwargs.get("file_extension", "") - if extension.lower() != ".pdf": - return None - - return DocumentConverterResult( - title=None, - text_content=pdfminer.high_level.extract_text(local_path), - ) - - -class DocxConverter(HtmlConverter): - """ - Converts DOCX files to Markdown. Style information (e.g.m headings) and tables are preserved where possible. - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a DOCX - extension = kwargs.get("file_extension", "") - if extension.lower() != ".docx": - return None - - result = None - with open(local_path, "rb") as docx_file: - style_map = kwargs.get("style_map", None) - - result = mammoth.convert_to_html(docx_file, style_map=style_map) - html_content = result.value - result = self._convert(html_content) - - return result - - -class XlsxConverter(HtmlConverter): - """ - Converts XLSX files to Markdown, with each sheet presented as a separate Markdown table. - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a XLSX - extension = kwargs.get("file_extension", "") - if extension.lower() != ".xlsx": - return None - - sheets = pd.read_excel(local_path, sheet_name=None, engine="openpyxl") - md_content = "" - for s in sheets: - md_content += f"## {s}\n" - html_content = sheets[s].to_html(index=False) - md_content += self._convert(html_content).text_content.strip() + "\n\n" - - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) - - -class XlsConverter(HtmlConverter): - """ - Converts XLS files to Markdown, with each sheet presented as a separate Markdown table. - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a XLS - extension = kwargs.get("file_extension", "") - if extension.lower() != ".xls": - return None - - sheets = pd.read_excel(local_path, sheet_name=None, engine="xlrd") - md_content = "" - for s in sheets: - md_content += f"## {s}\n" - html_content = sheets[s].to_html(index=False) - md_content += self._convert(html_content).text_content.strip() + "\n\n" - - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) - - -class PptxConverter(HtmlConverter): - """ - Converts PPTX files to Markdown. Supports heading, tables and images with alt text. - """ - - def _get_llm_description( - self, llm_client, llm_model, image_blob, content_type, prompt=None - ): - if prompt is None or prompt.strip() == "": - prompt = "Write a detailed alt text for this image with less than 50 words." - - image_base64 = base64.b64encode(image_blob).decode("utf-8") - data_uri = f"data:{content_type};base64,{image_base64}" - - messages = [ - { - "role": "user", - "content": [ - { - "type": "image_url", - "image_url": { - "url": data_uri, - }, - }, - {"type": "text", "text": prompt}, - ], - } - ] - - response = llm_client.chat.completions.create( - model=llm_model, messages=messages - ) - return response.choices[0].message.content - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a PPTX - extension = kwargs.get("file_extension", "") - if extension.lower() != ".pptx": - return None - - md_content = "" - - presentation = pptx.Presentation(local_path) - slide_num = 0 - for slide in presentation.slides: - slide_num += 1 - - md_content += f"\n\n\n" - - title = slide.shapes.title - for shape in slide.shapes: - # Pictures - if self._is_picture(shape): - # https://github.com/scanny/python-pptx/pull/512#issuecomment-1713100069 - - llm_description = None - alt_text = None - - llm_client = kwargs.get("llm_client") - llm_model = kwargs.get("llm_model") - if llm_client is not None and llm_model is not None: - try: - llm_description = self._get_llm_description( - llm_client, - llm_model, - shape.image.blob, - shape.image.content_type, - ) - except Exception: - # Unable to describe with LLM - pass - - if not llm_description: - try: - alt_text = shape._element._nvXxPr.cNvPr.attrib.get( - "descr", "" - ) - except Exception: - # Unable to get alt text - pass - - # A placeholder name - filename = re.sub(r"\W", "", shape.name) + ".jpg" - md_content += ( - "\n![" - + (llm_description or alt_text or shape.name) - + "](" - + filename - + ")\n" - ) - - # Tables - if self._is_table(shape): - html_table = "" - first_row = True - for row in shape.table.rows: - html_table += "" - for cell in row.cells: - if first_row: - html_table += "" - else: - html_table += "" - html_table += "" - first_row = False - html_table += "
" + html.escape(cell.text) + "" + html.escape(cell.text) + "
" - md_content += ( - "\n" + self._convert(html_table).text_content.strip() + "\n" - ) - - # Charts - if shape.has_chart: - md_content += self._convert_chart_to_markdown(shape.chart) - - # Text areas - elif shape.has_text_frame: - if shape == title: - md_content += "# " + shape.text.lstrip() + "\n" - else: - md_content += shape.text + "\n" - - md_content = md_content.strip() - - if slide.has_notes_slide: - md_content += "\n\n### Notes:\n" - notes_frame = slide.notes_slide.notes_text_frame - if notes_frame is not None: - md_content += notes_frame.text - md_content = md_content.strip() - - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) - - def _is_picture(self, shape): - if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PICTURE: - return True - if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PLACEHOLDER: - if hasattr(shape, "image"): - return True - return False - - def _is_table(self, shape): - if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.TABLE: - return True - return False - - def _convert_chart_to_markdown(self, chart): - md = "\n\n### Chart" - if chart.has_title: - md += f": {chart.chart_title.text_frame.text}" - md += "\n\n" - data = [] - category_names = [c.label for c in chart.plots[0].categories] - series_names = [s.name for s in chart.series] - data.append(["Category"] + series_names) - - for idx, category in enumerate(category_names): - row = [category] - for series in chart.series: - row.append(series.values[idx]) - data.append(row) - - markdown_table = [] - for row in data: - markdown_table.append("| " + " | ".join(map(str, row)) + " |") - header = markdown_table[0] - separator = "|" + "|".join(["---"] * len(data[0])) + "|" - return md + "\n".join([header, separator] + markdown_table[1:]) - - -class MediaConverter(DocumentConverter): - """ - Abstract class for multi-modal media (e.g., images and audio) - """ - - def _get_metadata(self, local_path, exiftool_path=None): - if not exiftool_path: - which_exiftool = shutil.which("exiftool") - if which_exiftool: - warn( - f"""Implicit discovery of 'exiftool' is disabled. If you would like to continue to use exiftool in MarkItDown, please set the exiftool_path parameter in the MarkItDown consructor. E.g., - - md = MarkItDown(exiftool_path="{which_exiftool}") - -This warning will be removed in future releases. -""", - DeprecationWarning, - ) - - return None - else: - try: - result = subprocess.run( - [exiftool_path, "-json", local_path], capture_output=True, text=True - ).stdout - return json.loads(result)[0] - except Exception: - return None - - -class WavConverter(MediaConverter): - """ - Converts WAV files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` is installed). - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a WAV - extension = kwargs.get("file_extension", "") - if extension.lower() != ".wav": - return None - - md_content = "" - - # Add metadata - metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) - if metadata: - for f in [ - "Title", - "Artist", - "Author", - "Band", - "Album", - "Genre", - "Track", - "DateTimeOriginal", - "CreateDate", - "Duration", - ]: - if f in metadata: - md_content += f"{f}: {metadata[f]}\n" - - # Transcribe - if IS_AUDIO_TRANSCRIPTION_CAPABLE: - try: - transcript = self._transcribe_audio(local_path) - md_content += "\n\n### Audio Transcript:\n" + ( - "[No speech detected]" if transcript == "" else transcript - ) - except Exception: - md_content += ( - "\n\n### Audio Transcript:\nError. Could not transcribe this audio." - ) - - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) - - def _transcribe_audio(self, local_path) -> str: - recognizer = sr.Recognizer() - with sr.AudioFile(local_path) as source: - audio = recognizer.record(source) - return recognizer.recognize_google(audio).strip() - - -class Mp3Converter(WavConverter): - """ - Converts MP3 files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` AND `pydub` are installed). - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a MP3 - extension = kwargs.get("file_extension", "") - if extension.lower() != ".mp3": - return None - - md_content = "" - - # Add metadata - metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) - if metadata: - for f in [ - "Title", - "Artist", - "Author", - "Band", - "Album", - "Genre", - "Track", - "DateTimeOriginal", - "CreateDate", - "Duration", - ]: - if f in metadata: - md_content += f"{f}: {metadata[f]}\n" - - # Transcribe - if IS_AUDIO_TRANSCRIPTION_CAPABLE: - handle, temp_path = tempfile.mkstemp(suffix=".wav") - os.close(handle) - try: - sound = pydub.AudioSegment.from_mp3(local_path) - sound.export(temp_path, format="wav") - - _args = dict() - _args.update(kwargs) - _args["file_extension"] = ".wav" - - try: - transcript = super()._transcribe_audio(temp_path).strip() - md_content += "\n\n### Audio Transcript:\n" + ( - "[No speech detected]" if transcript == "" else transcript - ) - except Exception: - md_content += "\n\n### Audio Transcript:\nError. Could not transcribe this audio." - - finally: - os.unlink(temp_path) - - # Return the result - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) - - -class ImageConverter(MediaConverter): - """ - Converts images to markdown via extraction of metadata (if `exiftool` is installed), OCR (if `easyocr` is installed), and description via a multimodal LLM (if an llm_client is configured). - """ - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not an image - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".jpg", ".jpeg", ".png"]: - return None - - md_content = "" - - # Add metadata - metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) - if metadata: - for f in [ - "ImageSize", - "Title", - "Caption", - "Description", - "Keywords", - "Artist", - "Author", - "DateTimeOriginal", - "CreateDate", - "GPSPosition", - ]: - if f in metadata: - md_content += f"{f}: {metadata[f]}\n" - - # Try describing the image with GPTV - llm_client = kwargs.get("llm_client") - llm_model = kwargs.get("llm_model") - if llm_client is not None and llm_model is not None: - md_content += ( - "\n# Description:\n" - + self._get_llm_description( - local_path, - extension, - llm_client, - llm_model, - prompt=kwargs.get("llm_prompt"), - ).strip() - + "\n" - ) - - return DocumentConverterResult( - title=None, - text_content=md_content, - ) - - def _get_llm_description(self, local_path, extension, client, model, prompt=None): - if prompt is None or prompt.strip() == "": - prompt = "Write a detailed caption for this image." - - data_uri = "" - with open(local_path, "rb") as image_file: - content_type, encoding = mimetypes.guess_type("_dummy" + extension) - if content_type is None: - content_type = "image/jpeg" - image_base64 = base64.b64encode(image_file.read()).decode("utf-8") - data_uri = f"data:{content_type};base64,{image_base64}" - - messages = [ - { - "role": "user", - "content": [ - {"type": "text", "text": prompt}, - { - "type": "image_url", - "image_url": { - "url": data_uri, - }, - }, - ], - } - ] - - response = client.chat.completions.create(model=model, messages=messages) - return response.choices[0].message.content - - -class OutlookMsgConverter(DocumentConverter): - """Converts Outlook .msg files to markdown by extracting email metadata and content. - - Uses the olefile package to parse the .msg file structure and extract: - - Email headers (From, To, Subject) - - Email body content - """ - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not a MSG file - extension = kwargs.get("file_extension", "") - if extension.lower() != ".msg": - return None - - try: - msg = olefile.OleFileIO(local_path) - # Extract email metadata - md_content = "# Email Message\n\n" - - # Get headers - headers = { - "From": self._get_stream_data(msg, "__substg1.0_0C1F001F"), - "To": self._get_stream_data(msg, "__substg1.0_0E04001F"), - "Subject": self._get_stream_data(msg, "__substg1.0_0037001F"), - } - - # Add headers to markdown - for key, value in headers.items(): - if value: - md_content += f"**{key}:** {value}\n" - - md_content += "\n## Content\n\n" - - # Get email body - body = self._get_stream_data(msg, "__substg1.0_1000001F") - if body: - md_content += body - - msg.close() - - return DocumentConverterResult( - title=headers.get("Subject"), text_content=md_content.strip() - ) - - except Exception as e: - raise FileConversionException( - f"Could not convert MSG file '{local_path}': {str(e)}" - ) - - def _get_stream_data( - self, msg: olefile.OleFileIO, stream_path: str - ) -> Union[str, None]: - """Helper to safely extract and decode stream data from the MSG file.""" - try: - if msg.exists(stream_path): - data = msg.openstream(stream_path).read() - # Try UTF-16 first (common for .msg files) - try: - return data.decode("utf-16-le").strip() - except UnicodeDecodeError: - # Fall back to UTF-8 - try: - return data.decode("utf-8").strip() - except UnicodeDecodeError: - # Last resort - ignore errors - return data.decode("utf-8", errors="ignore").strip() - except Exception: - pass - return None - - -class ZipConverter(DocumentConverter): - """Converts ZIP files to markdown by extracting and converting all contained files. - - The converter extracts the ZIP contents to a temporary directory, processes each file - using appropriate converters based on file extensions, and then combines the results - into a single markdown document. The temporary directory is cleaned up after processing. - - Example output format: - ```markdown - Content from the zip file `example.zip`: - - ## File: docs/readme.txt - - This is the content of readme.txt - Multiple lines are preserved - - ## File: images/example.jpg - - ImageSize: 1920x1080 - DateTimeOriginal: 2024-02-15 14:30:00 - Description: A beautiful landscape photo - - ## File: data/report.xlsx - - ## Sheet1 - | Column1 | Column2 | Column3 | - |---------|---------|---------| - | data1 | data2 | data3 | - | data4 | data5 | data6 | - ``` - - Key features: - - Maintains original file structure in headings - - Processes nested files recursively - - Uses appropriate converters for each file type - - Preserves formatting of converted content - - Cleans up temporary files after processing - """ - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not a ZIP - extension = kwargs.get("file_extension", "") - if extension.lower() != ".zip": - return None - - # Get parent converters list if available - parent_converters = kwargs.get("_parent_converters", []) - if not parent_converters: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] No converters available to process zip contents from: {local_path}", - ) - - extracted_zip_folder_name = ( - f"extracted_{os.path.basename(local_path).replace('.zip', '_zip')}" - ) - extraction_dir = os.path.normpath( - os.path.join(os.path.dirname(local_path), extracted_zip_folder_name) - ) - md_content = f"Content from the zip file `{os.path.basename(local_path)}`:\n\n" - - try: - # Extract the zip file safely - with zipfile.ZipFile(local_path, "r") as zipObj: - # Safeguard against path traversal - for member in zipObj.namelist(): - member_path = os.path.normpath(os.path.join(extraction_dir, member)) - if ( - not os.path.commonprefix([extraction_dir, member_path]) - == extraction_dir - ): - raise ValueError( - f"Path traversal detected in zip file: {member}" - ) - - # Extract all files safely - zipObj.extractall(path=extraction_dir) - - # Process each extracted file - for root, dirs, files in os.walk(extraction_dir): - for name in files: - file_path = os.path.join(root, name) - relative_path = os.path.relpath(file_path, extraction_dir) - - # Get file extension - _, file_extension = os.path.splitext(name) - - # Update kwargs for the file - file_kwargs = kwargs.copy() - file_kwargs["file_extension"] = file_extension - file_kwargs["_parent_converters"] = parent_converters - - # Try converting the file using available converters - for converter in parent_converters: - # Skip the zip converter to avoid infinite recursion - if isinstance(converter, ZipConverter): - continue - - result = converter.convert(file_path, **file_kwargs) - if result is not None: - md_content += f"\n## File: {relative_path}\n\n" - md_content += result.text_content + "\n\n" - break - - # Clean up extracted files if specified - if kwargs.get("cleanup_extracted", True): - shutil.rmtree(extraction_dir) - - return DocumentConverterResult(title=None, text_content=md_content.strip()) - - except zipfile.BadZipFile: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] Invalid or corrupted zip file: {local_path}", - ) - except ValueError as ve: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] Security error in zip file {local_path}: {str(ve)}", - ) - except Exception as e: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] Failed to process zip file {local_path}: {str(e)}", - ) - - -class DocumentIntelligenceConverter(DocumentConverter): - """Specialized DocumentConverter that uses Document Intelligence to extract text from documents.""" - - def __init__( - self, - endpoint: str, - api_version: str = "2024-07-31-preview", - ): - self.endpoint = endpoint - self.api_version = api_version - self.doc_intel_client = DocumentIntelligenceClient( - endpoint=self.endpoint, - api_version=self.api_version, - credential=DefaultAzureCredential(), - ) - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if extension is not supported by Document Intelligence - extension = kwargs.get("file_extension", "") - docintel_extensions = [ - ".pdf", - ".docx", - ".xlsx", - ".pptx", - ".html", - ".jpeg", - ".jpg", - ".png", - ".bmp", - ".tiff", - ".heif", - ] - if extension.lower() not in docintel_extensions: - return None - - # Get the bytestring for the local path - with open(local_path, "rb") as f: - file_bytes = f.read() - - # Certain document analysis features are not availiable for filetypes (.xlsx, .pptx, .html) - if extension.lower() in [".xlsx", ".pptx", ".html"]: - analysis_features = [] - else: - analysis_features = [ - DocumentAnalysisFeature.FORMULAS, # enable formula extraction - DocumentAnalysisFeature.OCR_HIGH_RESOLUTION, # enable high resolution OCR - DocumentAnalysisFeature.STYLE_FONT, # enable font style extraction - ] - - # Extract the text using Azure Document Intelligence - poller = self.doc_intel_client.begin_analyze_document( - model_id="prebuilt-layout", - body=AnalyzeDocumentRequest(bytes_source=file_bytes), - features=analysis_features, - output_content_format=CONTENT_FORMAT, # TODO: replace with "ContentFormat.MARKDOWN" when the bug is fixed - ) - result: AnalyzeResult = poller.result() - - # remove comments from the markdown content generated by Doc Intelligence and append to markdown string - markdown_text = re.sub(r"", "", result.content, flags=re.DOTALL) - return DocumentConverterResult( - title=None, - text_content=markdown_text, - ) - - -class FileConversionException(BaseException): - pass - - -class UnsupportedFormatException(BaseException): - pass - - -class MarkItDown: - """(In preview) An extremely simple text-based document reader, suitable for LLM use. - This reader will convert common file-types or webpages to Markdown.""" - - def __init__( - self, - requests_session: Optional[requests.Session] = None, - llm_client: Optional[Any] = None, - llm_model: Optional[str] = None, - style_map: Optional[str] = None, - exiftool_path: Optional[str] = None, - docintel_endpoint: Optional[str] = None, - # Deprecated - mlm_client: Optional[Any] = None, - mlm_model: Optional[str] = None, - ): - if requests_session is None: - self._requests_session = requests.Session() - else: - self._requests_session = requests_session - - if exiftool_path is None: - exiftool_path = os.environ.get("EXIFTOOL_PATH") - - # Handle deprecation notices - ############################# - if mlm_client is not None: - if llm_client is None: - warn( - "'mlm_client' is deprecated, and was renamed 'llm_client'.", - DeprecationWarning, - ) - llm_client = mlm_client - mlm_client = None - else: - raise ValueError( - "'mlm_client' is deprecated, and was renamed 'llm_client'. Do not use both at the same time. Just use 'llm_client' instead." - ) - - if mlm_model is not None: - if llm_model is None: - warn( - "'mlm_model' is deprecated, and was renamed 'llm_model'.", - DeprecationWarning, - ) - llm_model = mlm_model - mlm_model = None - else: - raise ValueError( - "'mlm_model' is deprecated, and was renamed 'llm_model'. Do not use both at the same time. Just use 'llm_model' instead." - ) - ############################# - - self._llm_client = llm_client - self._llm_model = llm_model - self._style_map = style_map - self._exiftool_path = exiftool_path - - self._page_converters: List[DocumentConverter] = [] - - # Register converters for successful browsing operations - # Later registrations are tried first / take higher priority than earlier registrations - # To this end, the most specific converters should appear below the most generic converters - self.register_page_converter(PlainTextConverter()) - self.register_page_converter(HtmlConverter()) - self.register_page_converter(RSSConverter()) - self.register_page_converter(WikipediaConverter()) - self.register_page_converter(YouTubeConverter()) - self.register_page_converter(BingSerpConverter()) - self.register_page_converter(DocxConverter()) - self.register_page_converter(XlsxConverter()) - self.register_page_converter(XlsConverter()) - self.register_page_converter(PptxConverter()) - self.register_page_converter(WavConverter()) - self.register_page_converter(Mp3Converter()) - self.register_page_converter(ImageConverter()) - self.register_page_converter(IpynbConverter()) - self.register_page_converter(PdfConverter()) - self.register_page_converter(ZipConverter()) - self.register_page_converter(OutlookMsgConverter()) - - # Register Document Intelligence converter at the top of the stack if endpoint is provided - if docintel_endpoint is not None: - self.register_page_converter( - DocumentIntelligenceConverter(endpoint=docintel_endpoint) - ) - - def convert( - self, source: Union[str, requests.Response, Path], **kwargs: Any - ) -> DocumentConverterResult: # TODO: deal with kwargs - """ - Args: - - source: can be a string representing a path either as string pathlib path object or url, or a requests.response object - - extension: specifies the file extension to use when interpreting the file. If None, infer from source (path, uri, content-type, etc.) - """ - - # Local path or url - if isinstance(source, str): - if ( - source.startswith("http://") - or source.startswith("https://") - or source.startswith("file://") - ): - return self.convert_url(source, **kwargs) - else: - return self.convert_local(source, **kwargs) - # Request response - elif isinstance(source, requests.Response): - return self.convert_response(source, **kwargs) - elif isinstance(source, Path): - return self.convert_local(source, **kwargs) - - def convert_local( - self, path: Union[str, Path], **kwargs: Any - ) -> DocumentConverterResult: # TODO: deal with kwargs - if isinstance(path, Path): - path = str(path) - # Prepare a list of extensions to try (in order of priority) - ext = kwargs.get("file_extension") - extensions = [ext] if ext is not None else [] - - # Get extension alternatives from the path and puremagic - base, ext = os.path.splitext(path) - self._append_ext(extensions, ext) - - for g in self._guess_ext_magic(path): - self._append_ext(extensions, g) - - # Convert - return self._convert(path, extensions, **kwargs) - - # TODO what should stream's type be? - def convert_stream( - self, stream: Any, **kwargs: Any - ) -> DocumentConverterResult: # TODO: deal with kwargs - # Prepare a list of extensions to try (in order of priority) - ext = kwargs.get("file_extension") - extensions = [ext] if ext is not None else [] - - # Save the file locally to a temporary file. It will be deleted before this method exits - handle, temp_path = tempfile.mkstemp() - fh = os.fdopen(handle, "wb") - result = None - try: - # Write to the temporary file - content = stream.read() - if isinstance(content, str): - fh.write(content.encode("utf-8")) - else: - fh.write(content) - fh.close() - - # Use puremagic to check for more extension options - for g in self._guess_ext_magic(temp_path): - self._append_ext(extensions, g) - - # Convert - result = self._convert(temp_path, extensions, **kwargs) - # Clean up - finally: - try: - fh.close() - except Exception: - pass - os.unlink(temp_path) - - return result - - def convert_url( - self, url: str, **kwargs: Any - ) -> DocumentConverterResult: # TODO: fix kwargs type - # Send a HTTP request to the URL - response = self._requests_session.get(url, stream=True) - response.raise_for_status() - return self.convert_response(response, **kwargs) - - def convert_response( - self, response: requests.Response, **kwargs: Any - ) -> DocumentConverterResult: # TODO fix kwargs type - # Prepare a list of extensions to try (in order of priority) - ext = kwargs.get("file_extension") - extensions = [ext] if ext is not None else [] - - # Guess from the mimetype - content_type = response.headers.get("content-type", "").split(";")[0] - self._append_ext(extensions, mimetypes.guess_extension(content_type)) - - # Read the content disposition if there is one - content_disposition = response.headers.get("content-disposition", "") - m = re.search(r"filename=([^;]+)", content_disposition) - if m: - base, ext = os.path.splitext(m.group(1).strip("\"'")) - self._append_ext(extensions, ext) - - # Read from the extension from the path - base, ext = os.path.splitext(urlparse(response.url).path) - self._append_ext(extensions, ext) - - # Save the file locally to a temporary file. It will be deleted before this method exits - handle, temp_path = tempfile.mkstemp() - fh = os.fdopen(handle, "wb") - result = None - try: - # Download the file - for chunk in response.iter_content(chunk_size=512): - fh.write(chunk) - fh.close() - - # Use puremagic to check for more extension options - for g in self._guess_ext_magic(temp_path): - self._append_ext(extensions, g) - - # Convert - result = self._convert(temp_path, extensions, url=response.url, **kwargs) - # Clean up - finally: - try: - fh.close() - except Exception: - pass - os.unlink(temp_path) - - return result - - def _convert( - self, local_path: str, extensions: List[Union[str, None]], **kwargs - ) -> DocumentConverterResult: - error_trace = "" - for ext in extensions + [None]: # Try last with no extension - for converter in self._page_converters: - _kwargs = copy.deepcopy(kwargs) - - # Overwrite file_extension appropriately - if ext is None: - if "file_extension" in _kwargs: - del _kwargs["file_extension"] - else: - _kwargs.update({"file_extension": ext}) - - # Copy any additional global options - if "llm_client" not in _kwargs and self._llm_client is not None: - _kwargs["llm_client"] = self._llm_client - - if "llm_model" not in _kwargs and self._llm_model is not None: - _kwargs["llm_model"] = self._llm_model - - if "style_map" not in _kwargs and self._style_map is not None: - _kwargs["style_map"] = self._style_map - - if "exiftool_path" not in _kwargs and self._exiftool_path is not None: - _kwargs["exiftool_path"] = self._exiftool_path - - # Add the list of converters for nested processing - _kwargs["_parent_converters"] = self._page_converters - - # If we hit an error log it and keep trying - try: - res = converter.convert(local_path, **_kwargs) - except Exception: - error_trace = ("\n\n" + traceback.format_exc()).strip() - - if res is not None: - # Normalize the content - res.text_content = "\n".join( - [line.rstrip() for line in re.split(r"\r?\n", res.text_content)] - ) - res.text_content = re.sub(r"\n{3,}", "\n\n", res.text_content) - - # Todo - return res - - # If we got this far without success, report any exceptions - if len(error_trace) > 0: - raise FileConversionException( - f"Could not convert '{local_path}' to Markdown. File type was recognized as {extensions}. While converting the file, the following error was encountered:\n\n{error_trace}" - ) - - # Nothing can handle it! - raise UnsupportedFormatException( - f"Could not convert '{local_path}' to Markdown. The formats {extensions} are not supported." - ) - - def _append_ext(self, extensions, ext): - """Append a unique non-None, non-empty extension to a list of extensions.""" - if ext is None: - return - ext = ext.strip() - if ext == "": - return - # if ext not in extensions: - extensions.append(ext) - - def _guess_ext_magic(self, path): - """Use puremagic (a Python implementation of libmagic) to guess a file's extension based on the first few bytes.""" - # Use puremagic to guess - try: - guesses = puremagic.magic_file(path) - - # Fix for: https://github.com/microsoft/markitdown/issues/222 - # If there are no guesses, then try again after trimming leading ASCII whitespaces. - # ASCII whitespace characters are those byte values in the sequence b' \t\n\r\x0b\f' - # (space, tab, newline, carriage return, vertical tab, form feed). - if len(guesses) == 0: - with open(path, "rb") as file: - while True: - char = file.read(1) - if not char: # End of file - break - if not char.isspace(): - file.seek(file.tell() - 1) - break - try: - guesses = puremagic.magic_stream(file) - except puremagic.main.PureError: - pass - - extensions = list() - for g in guesses: - ext = g.extension.strip() - if len(ext) > 0: - if not ext.startswith("."): - ext = "." + ext - if ext not in extensions: - extensions.append(ext) - return extensions - except FileNotFoundError: - pass - except IsADirectoryError: - pass - except PermissionError: - pass - return [] - - def register_page_converter(self, converter: DocumentConverter) -> None: - """Register a page text converter.""" - self._page_converters.insert(0, converter) From 4b625064519af97f8f0b905d352d13a31355ce08 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Mon, 10 Feb 2025 15:24:28 -0800 Subject: [PATCH 17/75] Small typo in README. --- packages/markitdown-sample-plugin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown-sample-plugin/README.md b/packages/markitdown-sample-plugin/README.md index 9b15ca0..06324cd 100644 --- a/packages/markitdown-sample-plugin/README.md +++ b/packages/markitdown-sample-plugin/README.md @@ -7,7 +7,7 @@ This project shows how to create a sample plugin for MarkItDown. The most important parts are as follows: -FNext, implement your custom DocumentConverter: +Next, implement your custom DocumentConverter: ```python from typing import Union From 3a5ca22a8df772ef37c2caa41198468442de4dbc Mon Sep 17 00:00:00 2001 From: Tomasz Kalinowski Date: Tue, 11 Feb 2025 10:13:17 -0500 Subject: [PATCH 18/75] Don't generate md links in 'pre' blocks (#322) --- packages/markitdown/src/markitdown/converters/_markdownify.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/markitdown/src/markitdown/converters/_markdownify.py b/packages/markitdown/src/markitdown/converters/_markdownify.py index 5b6d739..e15f607 100644 --- a/packages/markitdown/src/markitdown/converters/_markdownify.py +++ b/packages/markitdown/src/markitdown/converters/_markdownify.py @@ -33,6 +33,10 @@ class _CustomMarkdownify(markdownify.MarkdownConverter): prefix, suffix, text = markdownify.chomp(text) # type: ignore if not text: return "" + + if el.find_parent("pre") is not None: + return text + href = el.get("href") title = el.get("title") From 5ce85c236c5c456eac2f0ca7459b088d67f96250 Mon Sep 17 00:00:00 2001 From: Ruijun Gao Date: Wed, 12 Feb 2025 02:33:52 +0800 Subject: [PATCH 19/75] Fix a typo in sample RTF plugin (#320) --- .../src/markitdown_sample_plugin/_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py index c8c4fef..98e660e 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py @@ -23,7 +23,7 @@ class RtfConverter(DocumentConverter): """ def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a DOCX + # Bail if not a RTF extension = kwargs.get("file_extension", "") if extension.lower() != ".rtf": return None From 935da9976c9bbcc0cec6ca795ff245c8475d48fc Mon Sep 17 00:00:00 2001 From: afourney Date: Tue, 11 Feb 2025 12:36:32 -0800 Subject: [PATCH 20/75] Added priority argument to all converter constructors. (#324) * Added priority argument to all converter constructors. --- .../markitdown/src/markitdown/_markitdown.py | 13 +++----- .../src/markitdown/converters/_base.py | 31 ++++++++++++++++++- .../converters/_bing_serp_converter.py | 5 +++ .../converters/_doc_intel_converter.py | 4 +++ .../markitdown/converters/_docx_converter.py | 6 ++++ .../markitdown/converters/_html_converter.py | 5 +++ .../markitdown/converters/_image_converter.py | 7 ++++- .../markitdown/converters/_ipynb_converter.py | 5 +++ .../markitdown/converters/_media_converter.py | 11 +++++-- .../markitdown/converters/_mp3_converter.py | 7 ++++- .../converters/_outlook_msg_converter.py | 5 +++ .../markitdown/converters/_pdf_converter.py | 5 +++ .../converters/_plain_text_converter.py | 5 +++ .../markitdown/converters/_pptx_converter.py | 5 +++ .../markitdown/converters/_rss_converter.py | 5 +++ .../markitdown/converters/_wav_converter.py | 7 ++++- .../converters/_wikipedia_converter.py | 5 +++ .../markitdown/converters/_xlsx_converter.py | 7 ++++- .../converters/_youtube_converter.py | 5 +++ .../markitdown/converters/_zip_converter.py | 5 +++ packages/markitdown/tests/test_markitdown.py | 6 ++-- 21 files changed, 135 insertions(+), 19 deletions(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index b7ac5bc..297f554 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -47,10 +47,6 @@ from ._exceptions import ( # Override mimetype for csv to fix issue on windows mimetypes.add_type("text/csv", ".csv") -PRIORITY_SPECIFIC_FILE_FORMAT = 0.0 -PRIORITY_GENERIC_FILE_FORMAT = 10.0 - - _plugins: Union[None | List[Any]] = None @@ -123,6 +119,8 @@ class MarkItDown: self._llm_model = kwargs.get("llm_model") self._exiftool_path = kwargs.get("exiftool_path") self._style_map = kwargs.get("style_map") + if self._exiftool_path is None: + self._exiftool_path = os.getenv("EXIFTOOL_PATH") # Register converters for successful browsing operations # Later registrations are tried first / take higher priority than earlier registrations @@ -349,11 +347,10 @@ class MarkItDown: _kwargs["_parent_converters"] = self._page_converters # If we hit an error log it and keep trying - # try: - if True: + try: res = converter.convert(local_path, **_kwargs) - # except Exception: - # error_trace = ("\n\n" + traceback.format_exc()).strip() + except Exception: + error_trace = ("\n\n" + traceback.format_exc()).strip() if res is not None: # Normalize the content diff --git a/packages/markitdown/src/markitdown/converters/_base.py b/packages/markitdown/src/markitdown/converters/_base.py index 6d0a5a4..3947797 100644 --- a/packages/markitdown/src/markitdown/converters/_base.py +++ b/packages/markitdown/src/markitdown/converters/_base.py @@ -12,7 +12,36 @@ class DocumentConverterResult: class DocumentConverter: """Abstract superclass of all DocumentConverters.""" - def __init__(self, priority: float = 0.0): + # Lower priority values are tried first. + PRIORITY_SPECIFIC_FILE_FORMAT = ( + 0.0 # e.g., .docx, .pdf, .xlsx, Or specific pages, e.g., wikipedia + ) + PRIORITY_GENERIC_FILE_FORMAT = ( + 10.0 # Near catch-all converters for mimetypes like text/*, etc. + ) + + def __init__(self, priority: float = PRIORITY_SPECIFIC_FILE_FORMAT): + """ + Initialize the DocumentConverter with a given priority. + + Priorities work as follows: By default, most converters get priority + DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT (== 0). The exception + is the PlainTextConverter, which gets priority PRIORITY_SPECIFIC_FILE_FORMAT (== 10), + with lower values being tried first (i.e., higher priority). + + Just prior to conversion, the converters are sorted by priority, using + a stable sort. This means that converters with the same priority will + remain in the same order, with the most recently registered converters + appearing first. + + We have tight control over the order of built-in converters, but + plugins can register converters in any order. A converter's priority + field reasserts some control over the order of converters. + + Plugins can register converters with any priority, to appear before or + after the built-ins. For example, a plugin with priority 9 will run + before the PlainTextConverter, but after the built-in converters. + """ self._priority = priority def convert( diff --git a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py index b903724..d1b11a6 100644 --- a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py +++ b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py @@ -16,6 +16,11 @@ class BingSerpConverter(DocumentConverter): NOTE: It is better to use the Bing API """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a Bing SERP extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py index 94acc9f..835345a 100644 --- a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py +++ b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py @@ -22,9 +22,13 @@ class DocumentIntelligenceConverter(DocumentConverter): def __init__( self, + *, + priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT, endpoint: str, api_version: str = "2024-07-31-preview", ): + super().__init__(priority=priority) + self.endpoint = endpoint self.api_version = api_version self.doc_intel_client = DocumentIntelligenceClient( diff --git a/packages/markitdown/src/markitdown/converters/_docx_converter.py b/packages/markitdown/src/markitdown/converters/_docx_converter.py index fb61cca..8515f6d 100644 --- a/packages/markitdown/src/markitdown/converters/_docx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_docx_converter.py @@ -6,6 +6,7 @@ from ._base import ( DocumentConverterResult, ) +from ._base import DocumentConverter from ._html_converter import HtmlConverter @@ -14,6 +15,11 @@ class DocxConverter(HtmlConverter): Converts DOCX files to Markdown. Style information (e.g.m headings) and tables are preserved where possible. """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a DOCX extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_html_converter.py b/packages/markitdown/src/markitdown/converters/_html_converter.py index ae7259e..68c2536 100644 --- a/packages/markitdown/src/markitdown/converters/_html_converter.py +++ b/packages/markitdown/src/markitdown/converters/_html_converter.py @@ -8,6 +8,11 @@ from ._markdownify import _CustomMarkdownify class HtmlConverter(DocumentConverter): """Anything with content type text/html""" + def __init__( + self, priority: float = DocumentConverter.PRIORITY_GENERIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_image_converter.py b/packages/markitdown/src/markitdown/converters/_image_converter.py index f3dee6b..a46b67c 100644 --- a/packages/markitdown/src/markitdown/converters/_image_converter.py +++ b/packages/markitdown/src/markitdown/converters/_image_converter.py @@ -1,5 +1,5 @@ from typing import Union -from ._base import DocumentConverterResult +from ._base import DocumentConverter, DocumentConverterResult from ._media_converter import MediaConverter @@ -8,6 +8,11 @@ class ImageConverter(MediaConverter): Converts images to markdown via extraction of metadata (if `exiftool` is installed), OCR (if `easyocr` is installed), and description via a multimodal LLM (if an llm_client is configured). """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not an image extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_ipynb_converter.py b/packages/markitdown/src/markitdown/converters/_ipynb_converter.py index cdeb478..b487f41 100644 --- a/packages/markitdown/src/markitdown/converters/_ipynb_converter.py +++ b/packages/markitdown/src/markitdown/converters/_ipynb_converter.py @@ -12,6 +12,11 @@ from .._exceptions import FileConversionException class IpynbConverter(DocumentConverter): """Converts Jupyter Notebook (.ipynb) files to Markdown.""" + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_media_converter.py b/packages/markitdown/src/markitdown/converters/_media_converter.py index 07d2bde..5c7d82b 100644 --- a/packages/markitdown/src/markitdown/converters/_media_converter.py +++ b/packages/markitdown/src/markitdown/converters/_media_converter.py @@ -11,6 +11,11 @@ class MediaConverter(DocumentConverter): Abstract class for multi-modal media (e.g., images and audio) """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_GENERIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def _get_metadata(self, local_path, exiftool_path=None): if not exiftool_path: which_exiftool = shutil.which("exiftool") @@ -27,10 +32,10 @@ This warning will be removed in future releases. return None else: - try: + if True: result = subprocess.run( [exiftool_path, "-json", local_path], capture_output=True, text=True ).stdout return json.loads(result)[0] - except Exception: - return None + # except Exception: + # return None diff --git a/packages/markitdown/src/markitdown/converters/_mp3_converter.py b/packages/markitdown/src/markitdown/converters/_mp3_converter.py index 6b2786b..91fd270 100644 --- a/packages/markitdown/src/markitdown/converters/_mp3_converter.py +++ b/packages/markitdown/src/markitdown/converters/_mp3_converter.py @@ -1,6 +1,6 @@ import tempfile from typing import Union -from ._base import DocumentConverterResult +from ._base import DocumentConverter, DocumentConverterResult from ._wav_converter import WavConverter from warnings import resetwarnings, catch_warnings @@ -28,6 +28,11 @@ class Mp3Converter(WavConverter): Converts MP3 files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` AND `pydub` are installed). """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a MP3 extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py index e83001c..6764fc5 100644 --- a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py +++ b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py @@ -11,6 +11,11 @@ class OutlookMsgConverter(DocumentConverter): - Email body content """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index dcffc62..3a2b671 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -9,6 +9,11 @@ class PdfConverter(DocumentConverter): Converts PDFs to Markdown. Most style information is ignored, so the results are essentially plain-text. """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a PDF extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py index 2912d24..75f74a8 100644 --- a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py +++ b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py @@ -9,6 +9,11 @@ from ._base import DocumentConverter, DocumentConverterResult class PlainTextConverter(DocumentConverter): """Anything with content type text/plain""" + def __init__( + self, priority: float = DocumentConverter.PRIORITY_GENERIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index a48880a..afb37a0 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -14,6 +14,11 @@ class PptxConverter(HtmlConverter): Converts PPTX files to Markdown. Supports heading, tables and images with alt text. """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def _get_llm_description( self, llm_client, llm_model, image_blob, content_type, prompt=None ): diff --git a/packages/markitdown/src/markitdown/converters/_rss_converter.py b/packages/markitdown/src/markitdown/converters/_rss_converter.py index eb2f09c..b279c85 100644 --- a/packages/markitdown/src/markitdown/converters/_rss_converter.py +++ b/packages/markitdown/src/markitdown/converters/_rss_converter.py @@ -9,6 +9,11 @@ from ._base import DocumentConverter, DocumentConverterResult class RssConverter(DocumentConverter): """Convert RSS / Atom type to markdown""" + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_wav_converter.py b/packages/markitdown/src/markitdown/converters/_wav_converter.py index 6fc8932..3c8d842 100644 --- a/packages/markitdown/src/markitdown/converters/_wav_converter.py +++ b/packages/markitdown/src/markitdown/converters/_wav_converter.py @@ -1,5 +1,5 @@ from typing import Union -from ._base import DocumentConverterResult +from ._base import DocumentConverter, DocumentConverterResult from ._media_converter import MediaConverter # Optional Transcription support @@ -17,6 +17,11 @@ class WavConverter(MediaConverter): Converts WAV files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` is installed). """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a WAV extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py index 4097ef0..f27fe23 100644 --- a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py +++ b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py @@ -10,6 +10,11 @@ from ._markdownify import _CustomMarkdownify class WikipediaConverter(DocumentConverter): """Handle Wikipedia pages separately, focusing only on the main document content.""" + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py index 683349c..2bdfd5d 100644 --- a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py @@ -2,7 +2,7 @@ from typing import Union import pandas as pd -from ._base import DocumentConverterResult +from ._base import DocumentConverter, DocumentConverterResult from ._html_converter import HtmlConverter @@ -11,6 +11,11 @@ class XlsxConverter(HtmlConverter): Converts XLSX files to Markdown, with each sheet presented as a separate Markdown table. """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: # Bail if not a XLSX extension = kwargs.get("file_extension", "") diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py index fe198e8..b961b88 100644 --- a/packages/markitdown/src/markitdown/converters/_youtube_converter.py +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -19,6 +19,11 @@ except ModuleNotFoundError: class YouTubeConverter(DocumentConverter): """Handle YouTube specially, focusing on the video title, description, and transcript.""" + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/src/markitdown/converters/_zip_converter.py b/packages/markitdown/src/markitdown/converters/_zip_converter.py index 918c357..026900d 100644 --- a/packages/markitdown/src/markitdown/converters/_zip_converter.py +++ b/packages/markitdown/src/markitdown/converters/_zip_converter.py @@ -45,6 +45,11 @@ class ZipConverter(DocumentConverter): - Cleans up temporary files after processing """ + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py index be71722..efd45ac 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -327,8 +327,8 @@ def test_markitdown_llm() -> None: if __name__ == "__main__": """Runs this file's tests from the command line.""" - # test_markitdown_remote() - # test_markitdown_local() + test_markitdown_remote() + test_markitdown_local() test_markitdown_exiftool() - # test_markitdown_deprecation() # test_markitdown_llm() + print("All tests passed!") From 97eeed5f325d15ab3f1bc65e6a4f146c4e7e0680 Mon Sep 17 00:00:00 2001 From: KennyZhang1 <90438893+KennyZhang1@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:01:46 -0500 Subject: [PATCH 21/75] Doc Intelligence fixes for refactored code (#325) * added priority flag to doc intel converter constructor * fixed analysis features bug for docx --- .../src/markitdown/converters/_doc_intel_converter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py index 835345a..ed8aabf 100644 --- a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py +++ b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py @@ -1,4 +1,5 @@ from typing import Any, Union +import re # Azure imports from azure.ai.documentintelligence import DocumentIntelligenceClient @@ -36,6 +37,7 @@ class DocumentIntelligenceConverter(DocumentConverter): api_version=self.api_version, credential=DefaultAzureCredential(), ) + self._priority = priority def convert( self, local_path: str, **kwargs: Any @@ -62,8 +64,8 @@ class DocumentIntelligenceConverter(DocumentConverter): with open(local_path, "rb") as f: file_bytes = f.read() - # Certain document analysis features are not availiable for filetypes (.xlsx, .pptx, .html) - if extension.lower() in [".xlsx", ".pptx", ".html"]: + # Certain document analysis features are not availiable for office filetypes (.xlsx, .pptx, .html, .docx) + if extension.lower() in [".xlsx", ".pptx", ".html", ".docx"]: analysis_features = [] else: analysis_features = [ From dbdf2c0c1031dadc257a20f03cf9091907cb5972 Mon Sep 17 00:00:00 2001 From: afourney Date: Tue, 11 Feb 2025 20:42:50 -0800 Subject: [PATCH 22/75] Added CLI tests. (#327) --- .../markitdown/src/markitdown/__init__.py | 2 + packages/markitdown/tests/test_cli.py | 119 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 packages/markitdown/tests/test_cli.py diff --git a/packages/markitdown/src/markitdown/__init__.py b/packages/markitdown/src/markitdown/__init__.py index 5407233..59d9750 100644 --- a/packages/markitdown/src/markitdown/__init__.py +++ b/packages/markitdown/src/markitdown/__init__.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: MIT +from .__about__ import __version__ from ._markitdown import MarkItDown from ._exceptions import ( MarkItDownException, @@ -12,6 +13,7 @@ from ._exceptions import ( from .converters import DocumentConverter, DocumentConverterResult __all__ = [ + "__version__", "MarkItDown", "DocumentConverter", "DocumentConverterResult", diff --git a/packages/markitdown/tests/test_cli.py b/packages/markitdown/tests/test_cli.py new file mode 100644 index 0000000..1e2b095 --- /dev/null +++ b/packages/markitdown/tests/test_cli.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 -m pytest +import os +import subprocess +import pytest +from markitdown import __version__ + +try: + from .test_markitdown import TEST_FILES_DIR, DOCX_TEST_STRINGS +except ImportError: + from test_markitdown import TEST_FILES_DIR, DOCX_TEST_STRINGS + + +@pytest.fixture(scope="session") +def shared_tmp_dir(tmp_path_factory): + return tmp_path_factory.mktemp("pytest_tmp") + + +def test_version(shared_tmp_dir) -> None: + result = subprocess.run( + ["python", "-m", "markitdown", "--version"], capture_output=True, text=True + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert __version__ in result.stdout, f"Version not found in output: {result.stdout}" + + +def test_invalid_flag(shared_tmp_dir) -> None: + result = subprocess.run( + ["python", "-m", "markitdown", "--foobar"], capture_output=True, text=True + ) + + assert result.returncode != 0, f"CLI exited with error: {result.stderr}" + assert ( + "unrecognized arguments" in result.stderr + ), f"Expected 'unrecognized arguments' to appear in STDERR" + assert "SYNTAX" in result.stderr, f"Expected 'SYNTAX' to appear in STDERR" + + +def test_output_to_stdout(shared_tmp_dir) -> None: + # DOC X + result = subprocess.run( + ["python", "-m", "markitdown", os.path.join(TEST_FILES_DIR, "test.docx")], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + for test_string in DOCX_TEST_STRINGS: + assert ( + test_string in result.stdout + ), f"Expected string not found in output: {test_string}" + + +def test_output_to_file(shared_tmp_dir) -> None: + # DOC X, flag -o at the end + docx_output_file_1 = os.path.join(shared_tmp_dir, "test_docx_1.md") + result = subprocess.run( + [ + "python", + "-m", + "markitdown", + os.path.join(TEST_FILES_DIR, "test.docx"), + "-o", + docx_output_file_1, + ], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert os.path.exists( + docx_output_file_1 + ), f"Output file not created: {docx_output_file_1}" + + with open(docx_output_file_1, "r") as f: + output = f.read() + for test_string in DOCX_TEST_STRINGS: + assert ( + test_string in output + ), f"Expected string not found in output: {test_string}" + + # DOC X, flag -o at the beginning + docx_output_file_2 = os.path.join(shared_tmp_dir, "test_docx_2.md") + result = subprocess.run( + [ + "python", + "-m", + "markitdown", + "-o", + docx_output_file_2, + os.path.join(TEST_FILES_DIR, "test.docx"), + ], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert os.path.exists( + docx_output_file_2 + ), f"Output file not created: {docx_output_file_2}" + + with open(docx_output_file_2, "r") as f: + output = f.read() + for test_string in DOCX_TEST_STRINGS: + assert ( + test_string in output + ), f"Expected string not found in output: {test_string}" + + +if __name__ == "__main__": + """Runs this file's tests from the command line.""" + import tempfile + + with tempfile.TemporaryDirectory() as tmp_dir: + test_version(tmp_dir) + test_invalid_flag(tmp_dir) + test_output_to_stdout(tmp_dir) + test_output_to_file(tmp_dir) + print("All tests passed!") From e4b419ba4099ed5463627ab58a99e025780d20eb Mon Sep 17 00:00:00 2001 From: afourney Date: Thu, 27 Feb 2025 23:09:33 -0800 Subject: [PATCH 23/75] Pin Markdownify version. (#1069) * Pin markdownify version. TODO: update code for compatibility with Markdownify 1.0.0 --- packages/markitdown/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index 2a4e203..a321fee 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "beautifulsoup4", "requests", "mammoth", - "markdownify", + "markdownify~=0.14.1", "numpy", "python-pptx", "pandas", From d0ed74fdf4708e9931d8bc4dc5c711b1e9d444fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Menezes?= <77670471+menezesandre@users.noreply.github.com> Date: Fri, 28 Feb 2025 07:11:27 +0000 Subject: [PATCH 24/75] Fix UnboundLocalError in MarkItDown._convert (#1038) Initialize `res` at the beginning of `_convert`. If the first converter raises an exception, then the `res` variable was not initialized and we got an error when checking `if res is not None` --- packages/markitdown/src/markitdown/_markitdown.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 297f554..7c8d006 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -312,6 +312,7 @@ class MarkItDown: def _convert( self, local_path: str, extensions: List[Union[str, None]], **kwargs ) -> DocumentConverterResult: + res: Union[None, DocumentConverterResult] = None error_trace = "" # Create a copy of the page_converters list, sorted by priority. From a87fbf01ee663bae6a76cc6e076372fcfc832d9c Mon Sep 17 00:00:00 2001 From: tanreinama Date: Fri, 28 Feb 2025 16:16:09 +0900 Subject: [PATCH 25/75] add necessary imports (#861) * add necessary imports --- .../markitdown/src/markitdown/converters/_image_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/markitdown/src/markitdown/converters/_image_converter.py b/packages/markitdown/src/markitdown/converters/_image_converter.py index a46b67c..3c848dd 100644 --- a/packages/markitdown/src/markitdown/converters/_image_converter.py +++ b/packages/markitdown/src/markitdown/converters/_image_converter.py @@ -1,6 +1,8 @@ from typing import Union from ._base import DocumentConverter, DocumentConverterResult from ._media_converter import MediaConverter +import base64 +import mimetypes class ImageConverter(MediaConverter): From a394cc7c2738c8b5395fd2c55824e3d85cd6307d Mon Sep 17 00:00:00 2001 From: Nima Akbarzadeh Date: Fri, 28 Feb 2025 08:17:54 +0100 Subject: [PATCH 26/75] fix: Implement retry logic for YouTube transcript fetching and fix URL decoding issue (#1035) * fix: add error handling, refactor _findKey to use json.items() * fix: improve metadata and description extraction logic * fix: improve YouTube transcript extraction reliability * fix: implement retry logic for YouTube transcript fetching and fix URL decoding issue * fix(readme): add youtube URLs as markitdown supports --- README.md | 45 ++++----- .../converters/_youtube_converter.py | 97 +++++++++++++------ packages/markitdown/tests/test_markitdown.py | 6 +- 3 files changed, 93 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 8ac2fe3..70f188d 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ MarkItDown is a utility for converting various files to Markdown (e.g., for indexing, text analysis, etc). It supports: + - PDF - PowerPoint - Word @@ -18,9 +19,10 @@ It supports: - HTML - Text-based formats (CSV, JSON, XML) - ZIP files (iterates over contents) +- Youtube URLs - ... and more! -To install MarkItDown, use pip: `pip install markitdown`. Alternatively, you can install it from the source: +To install MarkItDown, use pip: `pip install markitdown`. Alternatively, you can install it from the source: ```bash git clone git@github.com:microsoft/markitdown.git @@ -74,7 +76,6 @@ markitdown path-to-file.pdf -o document.md -d -e " output.md ``` - + ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a +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. @@ -134,13 +135,12 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio 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 ofcourse 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) | +| | 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) |
@@ -148,22 +148,24 @@ You can help by looking at issues or helping review PRs. Any issue or PR is welc - Navigate to the MarkItDown package: - ```sh - cd packages/markitdown - ``` + ```sh + cd packages/markitdown + ``` - Install `hatch` in your environment and run tests: - ```sh - pip install hatch # Other ways of installing hatch: https://hatch.pypa.io/dev/install/ - hatch shell - hatch test - ``` + + ```sh + pip install hatch # Other ways of installing hatch: https://hatch.pypa.io/dev/install/ + hatch shell + hatch test + ``` (Alternative) Use the Devcontainer which has all the dependencies installed: - ```sh - # Reopen the project in Devcontainer and run: - hatch test - ``` + + ```sh + # Reopen the project in Devcontainer and run: + hatch test + ``` - Run pre-commit checks before submitting a PR: `pre-commit run --all-files` @@ -171,7 +173,6 @@ You can help by looking at issues or helping review PRs. Any issue or PR is welc You can also contribute by creating and sharing 3rd party plugins. See `packages/markitdown-sample-plugin` for more details. - ## Trademarks This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py index b961b88..e61b208 100644 --- a/packages/markitdown/src/markitdown/converters/_youtube_converter.py +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -1,4 +1,7 @@ import re +import json +import urllib.parse +import time from typing import Any, Union, Dict, List from urllib.parse import parse_qs, urlparse @@ -13,7 +16,7 @@ try: IS_YOUTUBE_TRANSCRIPT_CAPABLE = True except ModuleNotFoundError: - pass + IS_YOUTUBE_TRANSCRIPT_CAPABLE = False class YouTubeConverter(DocumentConverter): @@ -24,6 +27,20 @@ class YouTubeConverter(DocumentConverter): ): super().__init__(priority=priority) + def retry_operation(self, operation, retries=3, delay=2): + """Retries the operation if it fails.""" + attempt = 0 + while attempt < retries: + try: + return operation() # Attempt the operation + except Exception as e: + print(f"Attempt {attempt + 1} failed: {e}") + if attempt < retries - 1: + time.sleep(delay) # Wait before retrying + attempt += 1 + # If all attempts fail, raise the last exception + raise Exception(f"Operation failed after {retries} attempts.") + def convert( self, local_path: str, **kwargs: Any ) -> Union[None, DocumentConverterResult]: @@ -32,38 +49,50 @@ class YouTubeConverter(DocumentConverter): if extension.lower() not in [".html", ".htm"]: return None url = kwargs.get("url", "") + + url = urllib.parse.unquote(url) + url = url.replace(r"\?", "?").replace(r"\=", "=") + if not url.startswith("https://www.youtube.com/watch?"): return None - # Parse the file - soup = None - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") + # Parse the file with error handling + try: + with open(local_path, "rt", encoding="utf-8") as fh: + soup = BeautifulSoup(fh.read(), "html.parser") + except Exception as e: + print(f"Error reading YouTube page: {e}") + return None + + if not soup.title or not soup.title.string: + return None # Read the meta tags - assert soup.title is not None and soup.title.string is not None metadata: Dict[str, str] = {"title": soup.title.string} for meta in soup(["meta"]): for a in meta.attrs: if a in ["itemprop", "property", "name"]: - metadata[meta[a]] = meta.get("content", "") + content = meta.get("content", "") + if content: # Only add non-empty content + metadata[meta[a]] = content break - # We can also try to read the full description. This is more prone to breaking, since it reaches into the page implementation + # Try reading the description try: for script in soup(["script"]): - content = script.text + if not script.string: # Skip empty scripts + continue + content = script.string if "ytInitialData" in content: - lines = re.split(r"\r?\n", content) - obj_start = lines[0].find("{") - obj_end = lines[0].rfind("}") - if obj_start >= 0 and obj_end >= 0: - data = json.loads(lines[0][obj_start : obj_end + 1]) - attrdesc = self._findKey(data, "attributedDescriptionBodyText") # type: ignore - if attrdesc: - metadata["description"] = str(attrdesc["content"]) + match = re.search(r"var ytInitialData = ({.*?});", content) + if match: + data = json.loads(match.group(1)) + attrdesc = self._findKey(data, "attributedDescriptionBodyText") + if attrdesc and isinstance(attrdesc, dict): + metadata["description"] = str(attrdesc.get("content", "")) break - except Exception: + except Exception as e: + print(f"Error extracting description: {e}") pass # Start preparing the page @@ -99,21 +128,29 @@ class YouTubeConverter(DocumentConverter): transcript_text = "" parsed_url = urlparse(url) # type: ignore params = parse_qs(parsed_url.query) # type: ignore - if "v" in params: - assert isinstance(params["v"][0], str) + if "v" in params and params["v"][0]: video_id = str(params["v"][0]) try: youtube_transcript_languages = kwargs.get( "youtube_transcript_languages", ("en",) ) - # Must be a single transcript. - transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=youtube_transcript_languages) # type: ignore - transcript_text = " ".join([part["text"] for part in transcript]) # type: ignore + # Retry the transcript fetching operation + transcript = self.retry_operation( + lambda: YouTubeTranscriptApi.get_transcript( + video_id, languages=youtube_transcript_languages + ), + retries=3, # Retry 3 times + delay=2, # 2 seconds delay between retries + ) + if transcript: + transcript_text = " ".join( + [part["text"] for part in transcript] + ) # type: ignore # Alternative formatting: # formatter = TextFormatter() # formatter.format_transcript(transcript) - except Exception: - pass + except Exception as e: + print(f"Error fetching transcript: {e}") if transcript_text: webpage_text += f"\n### Transcript\n{transcript_text}\n" @@ -131,23 +168,23 @@ class YouTubeConverter(DocumentConverter): keys: List[str], default: Union[str, None] = None, ) -> Union[str, None]: + """Get first non-empty value from metadata matching given keys.""" for k in keys: if k in metadata: return metadata[k] return default def _findKey(self, json: Any, key: str) -> Union[str, None]: # TODO: Fix json type + """Recursively search for a key in nested dictionary/list structures.""" if isinstance(json, list): for elm in json: ret = self._findKey(elm, key) if ret is not None: return ret elif isinstance(json, dict): - for k in json: + for k, v in json.items(): if k == key: return json[k] - else: - ret = self._findKey(json[k], key) - if ret is not None: - return ret + if result := self._findKey(v, key): + return result return None diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py index efd45ac..55afcc3 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -184,9 +184,9 @@ def test_markitdown_remote() -> None: # Youtube # TODO: This test randomly fails for some reason. Haven't been able to repro it yet. Disabling until I can debug the issue - # result = markitdown.convert(YOUTUBE_TEST_URL) - # for test_string in YOUTUBE_TEST_STRINGS: - # assert test_string in result.text_content + result = markitdown.convert(YOUTUBE_TEST_URL) + for test_string in YOUTUBE_TEST_STRINGS: + assert test_string in result.text_content def test_markitdown_local() -> None: From e82e0c137271a0b4c2dee9375efdc43abc483004 Mon Sep 17 00:00:00 2001 From: Matthew Powers <121959840+C0dingMast3r@users.noreply.github.com> Date: Fri, 28 Feb 2025 02:21:51 -0500 Subject: [PATCH 27/75] Add Support For PPTX Shape Groups (Fix in code design to not miss out on slide content) (#331) * Adds support for Shape Groups * Update to Test PPtx for nested shape * This line was accidentally removed and is added back here --- .../markitdown/converters/_pptx_converter.py | 12 +++++++++++- .../markitdown/tests/test_files/test.pptx | Bin 124277 -> 127413 bytes 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index afb37a0..76c481a 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -64,7 +64,9 @@ class PptxConverter(HtmlConverter): md_content += f"\n\n\n" title = slide.shapes.title - for shape in slide.shapes: + + def get_shape_content(shape, **kwargs): + nonlocal md_content # Pictures if self._is_picture(shape): # https://github.com/scanny/python-pptx/pull/512#issuecomment-1713100069 @@ -134,6 +136,14 @@ class PptxConverter(HtmlConverter): else: md_content += shape.text + "\n" + # Group Shapes + if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.GROUP: + for subshape in shape.shapes: + get_shape_content(subshape, **kwargs) + + for shape in slide.shapes: + get_shape_content(shape, **kwargs) + md_content = md_content.strip() if slide.has_notes_slide: diff --git a/packages/markitdown/tests/test_files/test.pptx b/packages/markitdown/tests/test_files/test.pptx index ea1bbcb0f84f4e1007be35f2262173529215e484..e6d16f3cbb7836ff92b701b5fa61d92c165c50ab 100644 GIT binary patch delta 16933 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 BcVPek delta 14520 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 From 9a19fdd134b4de99ffbba6d50fd7848df6a6c50a Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 28 Feb 2025 07:43:03 -0800 Subject: [PATCH 28/75] Make sure extensions are unique in MarkItDown's convert methods. (#1076) --- packages/markitdown/src/markitdown/_markitdown.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 7c8d006..51f5c33 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -381,7 +381,8 @@ class MarkItDown: ext = ext.strip() if ext == "": return - # if ext not in extensions: + if ext in extensions: + return extensions.append(ext) def _guess_ext_magic(self, path): From 9182923375fb84892d88acaf3ca9d361bce53b0b Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 28 Feb 2025 09:54:19 -0800 Subject: [PATCH 29/75] Don't have ZipConverter accept OOXML files. This will never yield a good result. (#1078) --- .../markitdown/src/markitdown/converters/_zip_converter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/markitdown/src/markitdown/converters/_zip_converter.py b/packages/markitdown/src/markitdown/converters/_zip_converter.py index 026900d..e2b5fe6 100644 --- a/packages/markitdown/src/markitdown/converters/_zip_converter.py +++ b/packages/markitdown/src/markitdown/converters/_zip_converter.py @@ -77,6 +77,10 @@ class ZipConverter(DocumentConverter): try: # Extract the zip file safely with zipfile.ZipFile(local_path, "r") as zipObj: + # Bail if we discover it's an Office OOXML file + if "[Content_Types].xml" in zipObj.namelist(): + return None + # Safeguard against path traversal for member in zipObj.namelist(): member_path = os.path.normpath(os.path.join(extraction_dir, member)) From 43bd79adc98552ab994360940d207e32c8a59466 Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 28 Feb 2025 16:07:47 -0800 Subject: [PATCH 30/75] Print and log better exceptions when file conversions fail. (#1080) * Print and log better exceptions when file conversions fail. * Added unit tests for exceptions. --- .../markitdown/src/markitdown/__init__.py | 2 + .../markitdown/src/markitdown/_exceptions.py | 46 ++++++++++++++---- .../markitdown/src/markitdown/_markitdown.py | 20 +++++--- .../markitdown/tests/test_files/random.bin | Bin 0 -> 1024 bytes packages/markitdown/tests/test_markitdown.py | 18 ++++++- 5 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 packages/markitdown/tests/test_files/random.bin diff --git a/packages/markitdown/src/markitdown/__init__.py b/packages/markitdown/src/markitdown/__init__.py index 59d9750..c6d5363 100644 --- a/packages/markitdown/src/markitdown/__init__.py +++ b/packages/markitdown/src/markitdown/__init__.py @@ -7,6 +7,7 @@ from ._markitdown import MarkItDown from ._exceptions import ( MarkItDownException, ConverterPrerequisiteException, + FailedConversionAttempt, FileConversionException, UnsupportedFormatException, ) @@ -19,6 +20,7 @@ __all__ = [ "DocumentConverterResult", "MarkItDownException", "ConverterPrerequisiteException", + "FailedConversionAttempt", "FileConversionException", "UnsupportedFormatException", ] diff --git a/packages/markitdown/src/markitdown/_exceptions.py b/packages/markitdown/src/markitdown/_exceptions.py index 30c4dc5..50d2496 100644 --- a/packages/markitdown/src/markitdown/_exceptions.py +++ b/packages/markitdown/src/markitdown/_exceptions.py @@ -1,3 +1,6 @@ +from typing import Optional, List, Any + + class MarkItDownException(BaseException): """ Base exception class for MarkItDown. @@ -20,18 +23,43 @@ class ConverterPrerequisiteException(MarkItDownException): pass -class FileConversionException(MarkItDownException): - """ - Thrown when a suitable converter was found, but the conversion - process fails for any reason. - """ - - pass - - class UnsupportedFormatException(MarkItDownException): """ Thrown when no suitable converter was found for the given file. """ pass + + +class FailedConversionAttempt(object): + """ + Represents an a single attempt to convert a file. + """ + + def __init__(self, converter: Any, exc_info: Optional[tuple] = None): + self.converter = converter + self.exc_info = exc_info + + +class FileConversionException(MarkItDownException): + """ + Thrown when a suitable converter was found, but the conversion + process fails for any reason. + """ + + def __init__( + self, + message: Optional[str] = None, + attempts: Optional[List[FailedConversionAttempt]] = None, + ): + self.attempts = attempts + + if message is None: + if attempts is None: + message = "File conversion failed." + else: + message = f"File conversion failed after {len(attempts)} attempts:\n" + for attempt in attempts: + message += f" - {type(attempt.converter).__name__} threw {attempt.exc_info[0].__name__} with message: {attempt.exc_info[1]}\n" + + super().__init__(message) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 51f5c33..49b817d 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -2,6 +2,7 @@ import copy import mimetypes import os import re +import sys import tempfile import warnings import traceback @@ -42,6 +43,7 @@ from ._exceptions import ( FileConversionException, UnsupportedFormatException, ConverterPrerequisiteException, + FailedConversionAttempt, ) # Override mimetype for csv to fix issue on windows @@ -313,7 +315,9 @@ class MarkItDown: self, local_path: str, extensions: List[Union[str, None]], **kwargs ) -> DocumentConverterResult: res: Union[None, DocumentConverterResult] = None - error_trace = "" + + # Keep track of which converters throw exceptions + failed_attempts: List[FailedConversionAttempt] = [] # Create a copy of the page_converters list, sorted by priority. # We do this with each call to _convert because the priority of converters may change between calls. @@ -351,7 +355,11 @@ class MarkItDown: try: res = converter.convert(local_path, **_kwargs) except Exception: - error_trace = ("\n\n" + traceback.format_exc()).strip() + failed_attempts.append( + FailedConversionAttempt( + converter=converter, exc_info=sys.exc_info() + ) + ) if res is not None: # Normalize the content @@ -364,14 +372,12 @@ class MarkItDown: return res # If we got this far without success, report any exceptions - if len(error_trace) > 0: - raise FileConversionException( - f"Could not convert '{local_path}' to Markdown. File type was recognized as {extensions}. While converting the file, the following error was encountered:\n\n{error_trace}" - ) + if len(failed_attempts) > 0: + raise FileConversionException(attempts=failed_attempts) # Nothing can handle it! raise UnsupportedFormatException( - f"Could not convert '{local_path}' to Markdown. The formats {extensions} are not supported." + f"Could not convert '{local_path}' to Markdown. No converter attempted a conversion, suggesting that the filetype is simply not supported." ) def _append_ext(self, extensions, ext): diff --git a/packages/markitdown/tests/test_files/random.bin b/packages/markitdown/tests/test_files/random.bin new file mode 100644 index 0000000000000000000000000000000000000000..e673935e3fe247eb568b532edda79cfa6d7cfd23 GIT binary patch literal 1024 zcmV+b1poVZqDUF|I@MPm@I2ba$0^0xsbp5=o$th>YH+h(LnR-*n?R?s5~^bsp5l$X z7!7%l(k=#N0Jw)Mg|7LqVl#M>YgXS>F_7qT+Ta$BOcL@<=D?8&Tyzbza0M!}1$;|W zE280En|hWp5mqOnj+8dseB-OIb=`)1NzxJ`L~!o}I7|^;+VBtH0Eryk+!aOZQtBbu z+d)OC*Sj!C8hXD<05H0AteX&eRn;m*M-^4a)GnCF&Oy_Xtl2HYT$Q4Cl5VSs&Ep6m zBH}pJ@5V)56TBb@{sw}78^asVLNQvD&&bza?s=)<`Aum^Nwp9aYfaNz@n1(ZtEb3T zn1_qrM48Xdk_Sp=F9@=dlt0+n(CwH7(8{%|5AU1C`0=y}c8wJ(VA?k|Fxn_-03S<% z$$npfgt!B__+tzm3hvzTwmBh^;BS#Xmy#m!uK{z{@z~TV;a{SJXCFB0G0>@izrxvx zLDXFsvXHb$p}6ONcFotcO_yhBU>g^5`itQOtrNzb33~pIm^}+Nu)R^|`okXdo2=yR zql__p$hJ^g1zP)QpcN+YcA2?NB5#bK&KyY*C&eZHEFLGUm&Zeypx-Y>RQV_7eJE?j{;Fovw#+-GF2oxJtV}EyPNi0fN&@c zy$8z^dxzdPjCj{s>*ZM6XASd9ew9-p$NFR)ABLs}(MEP|*<4wZP9tC-Dmu2BTG{ez zM9(1*o!NO|rTs52NkHUb8t^@X;Gb=?g5W+B@PHq)d9}wi50J=^)9pDfUine~u}mW@EAyzV60qXX@FPW%u??=}_W#YoyH!tqHeq^X1rTURVlV uZ!;^~(Qhc=?1Mm6xcY{dXP^%X3G4bAv$;g8TUZ literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py index 55afcc3..0a3b56e 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -8,7 +8,7 @@ import requests from warnings import catch_warnings, resetwarnings -from markitdown import MarkItDown +from markitdown import MarkItDown, UnsupportedFormatException, FileConversionException skip_remote = ( True if os.environ.get("GITHUB_ACTIONS") else False @@ -272,6 +272,21 @@ def test_markitdown_local() -> None: assert "# Test" in result.text_content +def test_exceptions() -> None: + # Check that an exception is raised when trying to convert an unsupported format + markitdown = MarkItDown() + with pytest.raises(UnsupportedFormatException): + markitdown.convert(os.path.join(TEST_FILES_DIR, "random.bin")) + + # Check that an exception is raised when trying to convert a file that is corrupted + with pytest.raises(FileConversionException) as exc_info: + markitdown.convert( + os.path.join(TEST_FILES_DIR, "random.bin"), file_extension=".pptx" + ) + assert len(exc_info.value.attempts) == 1 + assert type(exc_info.value.attempts[0].converter).__name__ == "PptxConverter" + + @pytest.mark.skipif( skip_exiftool, reason="do not run if exiftool is not installed", @@ -329,6 +344,7 @@ if __name__ == "__main__": """Runs this file's tests from the command line.""" test_markitdown_remote() test_markitdown_local() + test_exceptions() test_markitdown_exiftool() # test_markitdown_llm() print("All tests passed!") From f01c6c52774ba284cf29796f44c62ad786030b7c Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 28 Feb 2025 16:28:35 -0800 Subject: [PATCH 31/75] Exceptions should subclass Exception not BaseException. (#1082) --- packages/markitdown/src/markitdown/_exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/_exceptions.py b/packages/markitdown/src/markitdown/_exceptions.py index 50d2496..4f443b2 100644 --- a/packages/markitdown/src/markitdown/_exceptions.py +++ b/packages/markitdown/src/markitdown/_exceptions.py @@ -1,7 +1,7 @@ from typing import Optional, List, Any -class MarkItDownException(BaseException): +class MarkItDownException(Exception): """ Base exception class for MarkItDown. """ From c5cd659f6381b40ccf5e7a936bb30c2f6a28b018 Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 3 Mar 2025 09:06:19 -0800 Subject: [PATCH 32/75] Exploring ways to allow Optional dependencies (#1079) * Enable optional dependencies. Starting with pptx. * Fix CLI tests.... have them install [all] * Added .docx to optional dependencies * Reuse error messages for missing dependencies. * Added xlsx and xls * Added pdfs * Added Ole files. * Updated READMEs, and finished remaining feature-categories. * Move OpenAI to hatch-test environment. --- README.md | 29 ++++++++++-- packages/markitdown/README.md | 4 +- packages/markitdown/pyproject.toml | 36 +++++++++++---- .../markitdown/src/markitdown/__init__.py | 4 +- .../markitdown/src/markitdown/_exceptions.py | 22 ++++++--- .../markitdown/src/markitdown/_markitdown.py | 1 - .../converters/_doc_intel_converter.py | 36 +++++++++++---- .../markitdown/converters/_docx_converter.py | 26 ++++++++++- .../markitdown/converters/_image_converter.py | 2 +- .../converters/_outlook_msg_converter.py | 32 +++++++++++-- .../markitdown/converters/_pdf_converter.py | 26 ++++++++++- .../converters/_plain_text_converter.py | 11 +++++ .../markitdown/converters/_pptx_converter.py | 25 ++++++++++- .../markitdown/converters/_xlsx_converter.py | 45 ++++++++++++++++++- 14 files changed, 254 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 70f188d..2563a68 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ [![Built by AutoGen Team](https://img.shields.io/badge/Built%20by-AutoGen%20Team-blue)](https://github.com/microsoft/autogen) > [!IMPORTANT] -> MarkItDown 0.0.2 alpha 1 (0.0.2a1) introduces a plugin-based architecture. As much as was possible, command-line and Python interfaces have remained the same as 0.0.1a3 to support backward compatibility. Please report any issues you encounter. Some interface changes may yet occur as we continue to refine MarkItDown to a first non-alpha release. +> Breaking changes between 0.0.1 to 0.0.2: +> * Dependencies are now organized into optional feature-groups (further details below). Use `pip install markitdown[all]` to have backward-compatible behavior. MarkItDown is a utility for converting various files to Markdown (e.g., for indexing, text analysis, etc). It supports: @@ -22,12 +23,12 @@ It supports: - Youtube URLs - ... and more! -To install MarkItDown, use pip: `pip install markitdown`. Alternatively, you can install it from the source: +To install MarkItDown, use pip: `pip install markitdown[all]`. Alternatively, you can install it from the source: ```bash git clone git@github.com:microsoft/markitdown.git cd markitdown -pip install -e packages/markitdown +pip install -e packages/markitdown[all] ``` ## Usage @@ -50,6 +51,28 @@ You can also pipe content: 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: diff --git a/packages/markitdown/README.md b/packages/markitdown/README.md index 54453ab..edd2701 100644 --- a/packages/markitdown/README.md +++ b/packages/markitdown/README.md @@ -10,7 +10,7 @@ From PyPI: ```bash -pip install markitdown +pip install markitdown[all] ``` From source: @@ -18,7 +18,7 @@ From source: ```bash git clone git@github.com:microsoft/markitdown.git cd markitdown -pip install -e packages/markitdown +pip install -e packages/markitdown[all] ``` ## Usage diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index a321fee..c053c7b 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -26,25 +26,36 @@ classifiers = [ dependencies = [ "beautifulsoup4", "requests", - "mammoth", "markdownify~=0.14.1", - "numpy", + "puremagic", + "pathvalidate", + "charset-normalizer", +] + +[project.optional-dependencies] +all = [ "python-pptx", + "mammoth", "pandas", "openpyxl", "xlrd", "pdfminer.six", - "puremagic", - "pydub", "olefile", - "youtube-transcript-api", + "pydub", "SpeechRecognition", - "pathvalidate", - "charset-normalizer", - "openai", + "youtube-transcript-api", "azure-ai-documentintelligence", "azure-identity" ] +pptx = ["python-pptx"] +docx = ["mammoth"] +xlsx = ["pandas", "openpyxl"] +xls = ["pandas", "xlrd"] +pdf = ["pdfminer.six"] +outlook = ["olefile"] +audio-transcription = ["pydub", "SpeechRecognition"] +youtube-transcription = ["youtube-transcript-api"] +az-doc-intel = ["azure-ai-documentintelligence", "azure-identity"] [project.urls] Documentation = "https://github.com/microsoft/markitdown#readme" @@ -57,6 +68,15 @@ path = "src/markitdown/__about__.py" [project.scripts] markitdown = "markitdown.__main__:main" +[tool.hatch.envs.default] +features = ["all"] + +[tool.hatch.envs.hatch-test] +features = ["all"] +extra-dependencies = [ + "openai", +] + [tool.hatch.envs.types] extra-dependencies = [ "mypy>=1.0.0", diff --git a/packages/markitdown/src/markitdown/__init__.py b/packages/markitdown/src/markitdown/__init__.py index c6d5363..9f7db16 100644 --- a/packages/markitdown/src/markitdown/__init__.py +++ b/packages/markitdown/src/markitdown/__init__.py @@ -6,7 +6,7 @@ from .__about__ import __version__ from ._markitdown import MarkItDown from ._exceptions import ( MarkItDownException, - ConverterPrerequisiteException, + MissingDependencyException, FailedConversionAttempt, FileConversionException, UnsupportedFormatException, @@ -19,7 +19,7 @@ __all__ = [ "DocumentConverter", "DocumentConverterResult", "MarkItDownException", - "ConverterPrerequisiteException", + "MissingDependencyException", "FailedConversionAttempt", "FileConversionException", "UnsupportedFormatException", diff --git a/packages/markitdown/src/markitdown/_exceptions.py b/packages/markitdown/src/markitdown/_exceptions.py index 4f443b2..abfebc6 100644 --- a/packages/markitdown/src/markitdown/_exceptions.py +++ b/packages/markitdown/src/markitdown/_exceptions.py @@ -1,5 +1,12 @@ from typing import Optional, List, Any +MISSING_DEPENDENCY_MESSAGE = """{converter} recognized the input as a potential {extension} file, but the dependencies needed to read {extension} files have not been installed. To resolve this error, include the optional dependency [{feature}] or [all] when installing MarkItDown. For example: + +* pip install markitdown[{feature}] +* pip install markitdown[all] +* pip install markitdown[{feature}, ...] +* etc.""" + class MarkItDownException(Exception): """ @@ -9,15 +16,16 @@ class MarkItDownException(Exception): pass -class ConverterPrerequisiteException(MarkItDownException): +class MissingDependencyException(MarkItDownException): """ - Thrown when instantiating a DocumentConverter in cases where - a required library or dependency is not installed, an API key - is not set, or some other prerequisite is not met. + Converters shipped with MarkItDown may depend on optional + dependencies. This exception is thrown when a converter's + convert() method is called, but the required dependency is not + installed. This is not necessarily a fatal error, as the converter + will simply be skipped (an error will bubble up only if no other + suitable converter is found). - This is not necessarily a fatal error. If thrown during - MarkItDown's plugin loading phase, the converter will simply be - skipped, and a warning will be issued. + Error messages should clearly indicate which dependency is missing. """ pass diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 49b817d..8f1bd46 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -42,7 +42,6 @@ from .converters import ( from ._exceptions import ( FileConversionException, UnsupportedFormatException, - ConverterPrerequisiteException, FailedConversionAttempt, ) diff --git a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py index ed8aabf..6fe79c0 100644 --- a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py +++ b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py @@ -1,16 +1,24 @@ from typing import Any, Union import re - -# Azure imports -from azure.ai.documentintelligence import DocumentIntelligenceClient -from azure.ai.documentintelligence.models import ( - AnalyzeDocumentRequest, - AnalyzeResult, - DocumentAnalysisFeature, -) -from azure.identity import DefaultAzureCredential +import sys from ._base import DocumentConverter, DocumentConverterResult +from .._exceptions import MissingDependencyException + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + from azure.ai.documentintelligence import DocumentIntelligenceClient + from azure.ai.documentintelligence.models import ( + AnalyzeDocumentRequest, + AnalyzeResult, + DocumentAnalysisFeature, + ) + from azure.identity import DefaultAzureCredential +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() # TODO: currently, there is a bug in the document intelligence SDK with importing the "ContentFormat" enum. @@ -30,6 +38,16 @@ class DocumentIntelligenceConverter(DocumentConverter): ): super().__init__(priority=priority) + # Raise an error if the dependencies are not available. + # This is different than other converters since this one isn't even instantiated + # unless explicitly requested. + if _dependency_exc_info is not None: + raise MissingDependencyException( + "DocumentIntelligenceConverter requires the optional dependency [az-doc-intel] (or [all]) to be installed. E.g., `pip install markitdown[az-doc-intel]`" + ) from _dependency_exc_info[1].with_traceback( + _dependency_exc_info[2] + ) # Restore the original traceback + self.endpoint = endpoint self.api_version = api_version self.doc_intel_client = DocumentIntelligenceClient( diff --git a/packages/markitdown/src/markitdown/converters/_docx_converter.py b/packages/markitdown/src/markitdown/converters/_docx_converter.py index 8515f6d..0866e59 100644 --- a/packages/markitdown/src/markitdown/converters/_docx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_docx_converter.py @@ -1,6 +1,6 @@ -from typing import Union +import sys -import mammoth +from typing import Union from ._base import ( DocumentConverterResult, @@ -8,6 +8,16 @@ from ._base import ( from ._base import DocumentConverter from ._html_converter import HtmlConverter +from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + import mammoth +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() class DocxConverter(HtmlConverter): @@ -26,6 +36,18 @@ class DocxConverter(HtmlConverter): if extension.lower() != ".docx": return None + # Check: the dependencies + if _dependency_exc_info is not None: + raise MissingDependencyException( + MISSING_DEPENDENCY_MESSAGE.format( + converter=type(self).__name__, + extension=".docx", + feature="docx", + ) + ) from _dependency_exc_info[1].with_traceback( + _dependency_exc_info[2] + ) # Restore the original traceback + result = None with open(local_path, "rb") as docx_file: style_map = kwargs.get("style_map", None) diff --git a/packages/markitdown/src/markitdown/converters/_image_converter.py b/packages/markitdown/src/markitdown/converters/_image_converter.py index 3c848dd..4eb6155 100644 --- a/packages/markitdown/src/markitdown/converters/_image_converter.py +++ b/packages/markitdown/src/markitdown/converters/_image_converter.py @@ -7,7 +7,7 @@ import mimetypes class ImageConverter(MediaConverter): """ - Converts images to markdown via extraction of metadata (if `exiftool` is installed), OCR (if `easyocr` is installed), and description via a multimodal LLM (if an llm_client is configured). + Converts images to markdown via extraction of metadata (if `exiftool` is installed), and description via a multimodal LLM (if an llm_client is configured). """ def __init__( diff --git a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py index 6764fc5..eb7a065 100644 --- a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py +++ b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py @@ -1,6 +1,16 @@ -import olefile +import sys from typing import Any, Union from ._base import DocumentConverter, DocumentConverterResult +from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + import olefile +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() class OutlookMsgConverter(DocumentConverter): @@ -24,6 +34,18 @@ class OutlookMsgConverter(DocumentConverter): if extension.lower() != ".msg": return None + # Check: the dependencies + if _dependency_exc_info is not None: + raise MissingDependencyException( + MISSING_DEPENDENCY_MESSAGE.format( + converter=type(self).__name__, + extension=".msg", + feature="outlook", + ) + ) from _dependency_exc_info[1].with_traceback( + _dependency_exc_info[2] + ) # Restore the original traceback + try: msg = olefile.OleFileIO(local_path) # Extract email metadata @@ -59,10 +81,12 @@ class OutlookMsgConverter(DocumentConverter): f"Could not convert MSG file '{local_path}': {str(e)}" ) - def _get_stream_data( - self, msg: olefile.OleFileIO, stream_path: str - ) -> Union[str, None]: + def _get_stream_data(self, msg: Any, stream_path: str) -> Union[str, None]: """Helper to safely extract and decode stream data from the MSG file.""" + assert isinstance( + msg, olefile.OleFileIO + ) # Ensure msg is of the correct type (type hinting is not possible with the optional olefile package) + try: if msg.exists(stream_path): data = msg.openstream(stream_path).read() diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index 3a2b671..3c5ecad 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -1,7 +1,17 @@ -import pdfminer -import pdfminer.high_level +import sys from typing import Union from ._base import DocumentConverter, DocumentConverterResult +from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + import pdfminer + import pdfminer.high_level +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() class PdfConverter(DocumentConverter): @@ -20,6 +30,18 @@ class PdfConverter(DocumentConverter): if extension.lower() != ".pdf": return None + # Check the dependencies + if _dependency_exc_info is not None: + raise MissingDependencyException( + MISSING_DEPENDENCY_MESSAGE.format( + converter=type(self).__name__, + extension=".pdf", + feature="pdf", + ) + ) from _dependency_exc_info[1].with_traceback( + _dependency_exc_info[2] + ) # Restore the original traceback + return DocumentConverterResult( title=None, text_content=pdfminer.high_level.extract_text(local_path), diff --git a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py index 75f74a8..b4c9282 100644 --- a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py +++ b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py @@ -6,6 +6,13 @@ from typing import Any, Union from ._base import DocumentConverter, DocumentConverterResult +# Mimetypes to ignore (commonly confused extensions) +IGNORE_MIMETYPES = [ + "text/vnd.in3d.spot", # .spo wich is confused with xls, doc, etc. + "text/vnd.graphviz", # .dot which is confused with xls, doc, etc. +] + + class PlainTextConverter(DocumentConverter): """Anything with content type text/plain""" @@ -22,6 +29,10 @@ class PlainTextConverter(DocumentConverter): "__placeholder" + kwargs.get("file_extension", "") ) + # Ignore common false positives + if content_type in IGNORE_MIMETYPES: + content_type = None + # Only accept text files if content_type is None: return None diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index 76c481a..431b6a0 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -1,12 +1,22 @@ import base64 -import pptx import re import html +import sys from typing import Union from ._base import DocumentConverterResult, DocumentConverter from ._html_converter import HtmlConverter +from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + import pptx +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() class PptxConverter(HtmlConverter): @@ -54,9 +64,20 @@ class PptxConverter(HtmlConverter): if extension.lower() != ".pptx": return None - md_content = "" + # Check the dependencies + if _dependency_exc_info is not None: + raise MissingDependencyException( + MISSING_DEPENDENCY_MESSAGE.format( + converter=type(self).__name__, + extension=".pptx", + feature="pptx", + ) + ) from _dependency_exc_info[1].with_traceback( + _dependency_exc_info[2] + ) # Restore the original traceback presentation = pptx.Presentation(local_path) + md_content = "" slide_num = 0 for slide in presentation.slides: slide_num += 1 diff --git a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py index 2bdfd5d..56398ca 100644 --- a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py @@ -1,9 +1,26 @@ -from typing import Union +import sys -import pandas as pd +from typing import Union from ._base import DocumentConverter, DocumentConverterResult from ._html_converter import HtmlConverter +from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_xlsx_dependency_exc_info = None +try: + import pandas as pd + import openpyxl +except ImportError: + _xlsx_dependency_exc_info = sys.exc_info() + +_xls_dependency_exc_info = None +try: + import pandas as pd + import xlrd +except ImportError: + _xls_dependency_exc_info = sys.exc_info() class XlsxConverter(HtmlConverter): @@ -22,6 +39,18 @@ class XlsxConverter(HtmlConverter): if extension.lower() != ".xlsx": return None + # Check the dependencies + if _xlsx_dependency_exc_info is not None: + raise MissingDependencyException( + MISSING_DEPENDENCY_MESSAGE.format( + converter=type(self).__name__, + extension=".xlsx", + feature="xlsx", + ) + ) from _xlsx_dependency_exc_info[1].with_traceback( + _xlsx_dependency_exc_info[2] + ) # Restore the original traceback + sheets = pd.read_excel(local_path, sheet_name=None, engine="openpyxl") md_content = "" for s in sheets: @@ -46,6 +75,18 @@ class XlsConverter(HtmlConverter): if extension.lower() != ".xls": return None + # Load the dependencies + if _xls_dependency_exc_info is not None: + raise MissingDependencyException( + MISSING_DEPENDENCY_MESSAGE.format( + converter=type(self).__name__, + extension=".xls", + feature="xls", + ) + ) from _xls_dependency_exc_info[1].with_traceback( + _xls_dependency_exc_info[2] + ) # Restore the original traceback + sheets = pd.read_excel(local_path, sheet_name=None, engine="xlrd") md_content = "" for s in sheets: From 1d2f231146c42713cc5c121c10004770267bdc0e Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 3 Mar 2025 09:45:36 -0800 Subject: [PATCH 33/75] Fixed property name (#1085) --- packages/markitdown/src/markitdown/converters/_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/markitdown/src/markitdown/converters/_base.py b/packages/markitdown/src/markitdown/converters/_base.py index 3947797..0f351fc 100644 --- a/packages/markitdown/src/markitdown/converters/_base.py +++ b/packages/markitdown/src/markitdown/converters/_base.py @@ -55,9 +55,9 @@ class DocumentConverter: return self._priority @priority.setter - def radius(self, value: float): + def priority(self, value: float): self._priority = value @priority.deleter - def radius(self): + def priority(self): raise AttributeError("Cannot delete the priority attribute") From e921497f794e8f6a6cdf9872d2a29e187448c7eb Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 5 Mar 2025 21:16:55 -0800 Subject: [PATCH 34/75] Update converter API, user streams rather than file paths (#1088) * Updated DocumentConverter interface * Updated all DocumentConverter classes * Added support for various new audio files. * Updated sample plugin to new DocumentConverter interface. * Updated project README with notes about changes, and use-cases. * Updated DocumentConverter documentation. * Move priority to outside DocumentConverter, allowing them to be reprioritized, and keeping the DocumentConverter interface simple. --------- Co-authored-by: Kenny Zhang --- .gitattributes | 3 +- README.md | 17 +- packages/markitdown-sample-plugin/README.md | 47 +- .../markitdown-sample-plugin/pyproject.toml | 2 +- .../src/markitdown_sample_plugin/__about__.py | 2 +- .../src/markitdown_sample_plugin/_plugin.py | 59 ++- .../tests/test_sample_plugin.py | 20 +- packages/markitdown/pyproject.toml | 7 +- .../markitdown/src/markitdown/__about__.py | 2 +- .../markitdown/src/markitdown/__init__.py | 12 +- .../src/markitdown/_base_converter.py | 108 ++++ .../markitdown/src/markitdown/_exceptions.py | 5 +- .../markitdown/src/markitdown/_markitdown.py | 484 +++++++++++------- .../markitdown/src/markitdown/_stream_info.py | 122 +++++ .../src/markitdown/converters/__init__.py | 9 +- .../markitdown/converters/_audio_converter.py | 102 ++++ .../src/markitdown/converters/_base.py | 63 --- .../converters/_bing_serp_converter.py | 73 ++- .../converters/_doc_intel_converter.py | 143 ++++-- .../markitdown/converters/_docx_converter.py | 70 ++- .../src/markitdown/converters/_exiftool.py | 44 ++ .../markitdown/converters/_html_converter.py | 84 ++- .../markitdown/converters/_image_converter.py | 114 +++-- .../markitdown/converters/_ipynb_converter.py | 69 ++- .../src/markitdown/converters/_llm_caption.py | 50 ++ .../src/markitdown/converters/_markdownify.py | 27 +- .../markitdown/converters/_media_converter.py | 41 -- .../markitdown/converters/_mp3_converter.py | 89 ---- .../converters/_outlook_msg_converter.py | 133 +++-- .../markitdown/converters/_pdf_converter.py | 60 ++- .../converters/_plain_text_converter.py | 79 +-- .../markitdown/converters/_pptx_converter.py | 183 ++++--- .../markitdown/converters/_rss_converter.py | 225 ++++---- .../converters/_transcribe_audio.py | 43 ++ .../markitdown/converters/_wav_converter.py | 72 --- .../converters/_wikipedia_converter.py | 72 ++- .../markitdown/converters/_xlsx_converter.py | 125 +++-- .../converters/_youtube_converter.py | 117 +++-- .../markitdown/converters/_zip_converter.py | 163 +++--- packages/markitdown/tests/test_cli.py | 2 +- packages/markitdown/tests/test_files/test.m4a | Bin 0 -> 165924 bytes packages/markitdown/tests/test_files/test.mp3 | Bin 0 -> 156258 bytes packages/markitdown/tests/test_files/test.pdf | Bin 0 -> 92971 bytes .../markitdown/tests/test_files/test.pptx | Bin 127413 -> 277515 bytes packages/markitdown/tests/test_files/test.wav | Bin 0 -> 1237070 bytes .../tests/test_files/test_notebook.ipynb | 174 +++---- packages/markitdown/tests/test_markitdown.py | 277 +++++++++- 47 files changed, 2329 insertions(+), 1264 deletions(-) create mode 100644 packages/markitdown/src/markitdown/_base_converter.py create mode 100644 packages/markitdown/src/markitdown/_stream_info.py create mode 100644 packages/markitdown/src/markitdown/converters/_audio_converter.py delete mode 100644 packages/markitdown/src/markitdown/converters/_base.py create mode 100644 packages/markitdown/src/markitdown/converters/_exiftool.py create mode 100644 packages/markitdown/src/markitdown/converters/_llm_caption.py delete mode 100644 packages/markitdown/src/markitdown/converters/_media_converter.py delete mode 100644 packages/markitdown/src/markitdown/converters/_mp3_converter.py create mode 100644 packages/markitdown/src/markitdown/converters/_transcribe_audio.py delete mode 100644 packages/markitdown/src/markitdown/converters/_wav_converter.py create mode 100755 packages/markitdown/tests/test_files/test.m4a create mode 100644 packages/markitdown/tests/test_files/test.mp3 create mode 100644 packages/markitdown/tests/test_files/test.pdf create mode 100644 packages/markitdown/tests/test_files/test.wav diff --git a/.gitattributes b/.gitattributes index d2f31ef..f787c0e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -tests/test_files/** linguist-vendored +packages/markitdown/tests/test_files/** linguist-vendored +packages/markitdown-sample-plugin/tests/test_files/** linguist-vendored diff --git a/README.md b/README.md index 2563a68..5f9ef70 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,11 @@ > [!IMPORTANT] > Breaking changes between 0.0.1 to 0.0.2: > * Dependencies are now organized into optional feature-groups (further details below). Use `pip install markitdown[all]` to have backward-compatible behavior. +> * 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. -MarkItDown is a utility for converting various files to Markdown (e.g., for indexing, text analysis, etc). -It supports: +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. + +At present, MarkItDown supports: - PDF - PowerPoint @@ -23,6 +25,17 @@ It supports: - Youtube URLs - ... 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. + +## Installation + To install MarkItDown, use pip: `pip install markitdown[all]`. Alternatively, you can install it from the source: ```bash diff --git a/packages/markitdown-sample-plugin/README.md b/packages/markitdown-sample-plugin/README.md index 06324cd..fd7115f 100644 --- a/packages/markitdown-sample-plugin/README.md +++ b/packages/markitdown-sample-plugin/README.md @@ -10,23 +10,38 @@ This project shows how to create a sample plugin for MarkItDown. The most import Next, implement your custom DocumentConverter: ```python -from typing import Union -from markitdown import DocumentConverter, DocumentConverterResult +from typing import BinaryIO, Any +from markitdown import MarkItDown, DocumentConverter, DocumentConverterResult, StreamInfo class RtfConverter(DocumentConverter): - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not an RTF file - extension = kwargs.get("file_extension", "") - if extension.lower() != ".rtf": - return None - # Implement the conversion logic here ... + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) - # Return the result - return DocumentConverterResult( - title=title, - text_content=text_content, - ) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, + ) -> bool: + + # Implement logic to check if the file stream is an RTF file + # ... + raise NotImplementedError() + + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, + ) -> DocumentConverterResult: + + # Implement logic to convert the file stream to Markdown + # ... + raise NotImplementedError() ``` Next, make sure your package implements and exports the following: @@ -71,10 +86,10 @@ Once the plugin package is installed, verify that it is available to MarkItDown markitdown --list-plugins ``` -To use the plugin for a conversion use the `--use-plugins` flag. For example, to convert a PDF: +To use the plugin for a conversion use the `--use-plugins` flag. For example, to convert an RTF file: ```bash -markitdown --use-plugins path-to-file.pdf +markitdown --use-plugins path-to-file.rtf ``` In Python, plugins can be enabled as follows: @@ -83,7 +98,7 @@ In Python, plugins can be enabled as follows: from markitdown import MarkItDown md = MarkItDown(enable_plugins=True) -result = md.convert("path-to-file.pdf") +result = md.convert("path-to-file.rtf") print(result.text_content) ``` diff --git a/packages/markitdown-sample-plugin/pyproject.toml b/packages/markitdown-sample-plugin/pyproject.toml index aaf2012..d8668aa 100644 --- a/packages/markitdown-sample-plugin/pyproject.toml +++ b/packages/markitdown-sample-plugin/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "markitdown", + "markitdown>=0.0.2a2", "striprtf", ] diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py index fa67ccb..a365900 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.0.1a2" +__version__ = "0.0.1a3" diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py index 98e660e..1362818 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py @@ -1,12 +1,26 @@ -from typing import Union +import locale +from typing import BinaryIO, Any from striprtf.striprtf import rtf_to_text -from markitdown import MarkItDown, DocumentConverter, DocumentConverterResult +from markitdown import ( + MarkItDown, + DocumentConverter, + DocumentConverterResult, + StreamInfo, +) + __plugin_interface_version__ = ( 1 # The version of the plugin interface that this plugin uses ) +ACCEPTED_MIME_TYPE_PREFIXES = [ + "text/rtf", + "application/rtf", +] + +ACCEPTED_FILE_EXTENSIONS = [".rtf"] + def register_converters(markitdown: MarkItDown, **kwargs): """ @@ -22,18 +36,41 @@ class RtfConverter(DocumentConverter): Converts an RTF file to in the simplest possible way. """ - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a RTF - extension = kwargs.get("file_extension", "") - if extension.lower() != ".rtf": - return None + def __init__( + self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + ): + super().__init__(priority=priority) - # Read the RTF file - with open(local_path, "r") as f: - rtf = f.read() + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, + ) -> DocumentConverterResult: + # Read the file stream into an str using hte provided charset encoding, or using the system default + encoding = stream_info.charset or locale.getpreferredencoding() + stream_data = file_stream.read().decode(encoding) # Return the result return DocumentConverterResult( title=None, - text_content=rtf_to_text(rtf), + markdown=rtf_to_text(stream_data), ) diff --git a/packages/markitdown-sample-plugin/tests/test_sample_plugin.py b/packages/markitdown-sample-plugin/tests/test_sample_plugin.py index 49d54aa..6d0102d 100644 --- a/packages/markitdown-sample-plugin/tests/test_sample_plugin.py +++ b/packages/markitdown-sample-plugin/tests/test_sample_plugin.py @@ -2,7 +2,7 @@ import os import pytest -from markitdown import MarkItDown +from markitdown import MarkItDown, StreamInfo from markitdown_sample_plugin import RtfConverter TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "test_files") @@ -15,18 +15,22 @@ RTF_TEST_STRINGS = { def test_converter() -> None: """Tests the RTF converter dirctly.""" - converter = RtfConverter() - result = converter.convert( - os.path.join(TEST_FILES_DIR, "test.rtf"), file_extension=".rtf" - ) + with open(os.path.join(TEST_FILES_DIR, "test.rtf"), "rb") as file_stream: + converter = RtfConverter() + result = converter.convert( + file_stream=file_stream, + stream_info=StreamInfo( + mimetype="text/rtf", extension=".rtf", filename="test.rtf" + ), + ) - for test_string in RTF_TEST_STRINGS: - assert test_string in result.text_content + for test_string in RTF_TEST_STRINGS: + assert test_string in result.text_content def test_markitdown() -> None: """Tests that MarkItDown correctly loads the plugin.""" - md = MarkItDown() + md = MarkItDown(enable_plugins=True) result = md.convert(os.path.join(TEST_FILES_DIR, "test.rtf")) for test_string in RTF_TEST_STRINGS: diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index c053c7b..d0f515e 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "beautifulsoup4", "requests", - "markdownify~=0.14.1", + "markdownify", "puremagic", "pathvalidate", "charset-normalizer", @@ -78,11 +78,14 @@ extra-dependencies = [ ] [tool.hatch.envs.types] +features = ["all"] extra-dependencies = [ + "openai", "mypy>=1.0.0", ] + [tool.hatch.envs.types.scripts] -check = "mypy --install-types --non-interactive {args:src/markitdown tests}" +check = "mypy --install-types --non-interactive --ignore-missing-imports {args:src/markitdown tests}" [tool.coverage.run] source_pkgs = ["markitdown", "tests"] diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index dc5aafc..4ebb498 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.0.2a1" +__version__ = "0.0.2a2" diff --git a/packages/markitdown/src/markitdown/__init__.py b/packages/markitdown/src/markitdown/__init__.py index 9f7db16..af356dd 100644 --- a/packages/markitdown/src/markitdown/__init__.py +++ b/packages/markitdown/src/markitdown/__init__.py @@ -3,7 +3,13 @@ # SPDX-License-Identifier: MIT from .__about__ import __version__ -from ._markitdown import MarkItDown +from ._markitdown import ( + MarkItDown, + PRIORITY_SPECIFIC_FILE_FORMAT, + PRIORITY_GENERIC_FILE_FORMAT, +) +from ._base_converter import DocumentConverterResult, DocumentConverter +from ._stream_info import StreamInfo from ._exceptions import ( MarkItDownException, MissingDependencyException, @@ -11,7 +17,6 @@ from ._exceptions import ( FileConversionException, UnsupportedFormatException, ) -from .converters import DocumentConverter, DocumentConverterResult __all__ = [ "__version__", @@ -23,4 +28,7 @@ __all__ = [ "FailedConversionAttempt", "FileConversionException", "UnsupportedFormatException", + "StreamInfo", + "PRIORITY_SPECIFIC_FILE_FORMAT", + "PRIORITY_GENERIC_FILE_FORMAT", ] diff --git a/packages/markitdown/src/markitdown/_base_converter.py b/packages/markitdown/src/markitdown/_base_converter.py new file mode 100644 index 0000000..2f0ca9d --- /dev/null +++ b/packages/markitdown/src/markitdown/_base_converter.py @@ -0,0 +1,108 @@ +import os +import tempfile +from warnings import warn +from typing import Any, Union, BinaryIO, Optional, List +from ._stream_info import StreamInfo + + +class DocumentConverterResult: + """The result of converting a document to Markdown.""" + + def __init__( + self, + markdown: str, + *, + title: Optional[str] = None, + ): + """ + Initialize the DocumentConverterResult. + + The only required parameter is the converted Markdown text. + The title, and any other metadata that may be added in the future, are optional. + + Parameters: + - markdown: The converted Markdown text. + - title: Optional title of the document. + """ + self.markdown = markdown + self.title = title + + @property + def text_content(self) -> str: + """Soft-deprecated alias for `markdown`. New code should migrate to using `markdown` or __str__.""" + return self.markdown + + @text_content.setter + def text_content(self, markdown: str): + """Soft-deprecated alias for `markdown`. New code should migrate to using `markdown` or __str__.""" + self.markdown = markdown + + def __str__(self) -> str: + """Return the converted Markdown text.""" + return self.markdown + + +class DocumentConverter: + """Abstract superclass of all DocumentConverters.""" + + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + """ + Return a quick determination on if the converter should attempt converting the document. + This is primarily based `stream_info` (typically, `stream_info.mimetype`, `stream_info.extension`). + In cases where the data is retrieved via HTTP, the `steam_info.url` might also be referenced to + make a determination (e.g., special converters for Wikipedia, YouTube etc). + Finally, it is conceivable that the `stream_info.filename` might be used to in cases + where the filename is well-known (e.g., `Dockerfile`, `Makefile`, etc) + + NOTE: The method signature is designed to match that of the convert() method. This provides some + assurance that, if accepts() returns True, the convert() method will also be able to handle the document. + + IMPORTANT: In rare cases, (e.g., OutlookMsgConverter) we need to read more from the stream to make a final + determination. Read operations inevitably advances the position in file_stream. In these case, the position + MUST be reset it MUST be reset before returning. This is because the convert() method may be called immediately + after accepts(), and will expect the file_stream to be at the original position. + + E.g., + cur_pos = file_stream.tell() # Save the current position + data = file_stream.read(100) # ... peek at the first 100 bytes, etc. + file_stream.seek(cur_pos) # Reset the position to the original position + + Prameters: + - file_stream: The file-like object to convert. Must support seek(), tell(), and read() methods. + - stream_info: The StreamInfo object containing metadata about the file (mimetype, extension, charset, set) + - kwargs: Additional keyword arguments for the converter. + + Returns: + - bool: True if the converter can handle the document, False otherwise. + """ + raise NotImplementedError( + f"The subclass, {type(self).__name__}, must implement the accepts() method to determine if they can handle the document." + ) + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + """ + Convert a document to Markdown text. + + Prameters: + - file_stream: The file-like object to convert. Must support seek(), tell(), and read() methods. + - stream_info: The StreamInfo object containing metadata about the file (mimetype, extension, charset, set) + - kwargs: Additional keyword arguments for the converter. + + Returns: + - DocumentConverterResult: The result of the conversion, which includes the title and markdown content. + + Raises: + - FileConversionException: If the mimetype is recognized, but the conversion fails for some other reason. + - MissingDependencyException: If the converter requires a dependency that is not installed. + """ + raise NotImplementedError("Subclasses must implement this method") diff --git a/packages/markitdown/src/markitdown/_exceptions.py b/packages/markitdown/src/markitdown/_exceptions.py index abfebc6..93f8f0e 100644 --- a/packages/markitdown/src/markitdown/_exceptions.py +++ b/packages/markitdown/src/markitdown/_exceptions.py @@ -68,6 +68,9 @@ class FileConversionException(MarkItDownException): else: message = f"File conversion failed after {len(attempts)} attempts:\n" for attempt in attempts: - message += f" - {type(attempt.converter).__name__} threw {attempt.exc_info[0].__name__} with message: {attempt.exc_info[1]}\n" + if attempt.exc_info is None: + message += " - {type(attempt.converter).__name__} provided no execution info." + else: + message += f" - {type(attempt.converter).__name__} threw {attempt.exc_info[0].__name__} with message: {attempt.exc_info[1]}\n" super().__init__(message) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 8f1bd46..6086eb9 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -6,8 +6,10 @@ import sys import tempfile import warnings import traceback +import io +from dataclasses import dataclass from importlib.metadata import entry_points -from typing import Any, List, Optional, Union +from typing import Any, List, Optional, Union, BinaryIO from pathlib import Path from urllib.parse import urlparse from warnings import warn @@ -16,9 +18,9 @@ from warnings import warn import puremagic import requests +from ._stream_info import StreamInfo, _guess_stream_info_from_stream + from .converters import ( - DocumentConverter, - DocumentConverterResult, PlainTextConverter, HtmlConverter, RssConverter, @@ -32,26 +34,34 @@ from .converters import ( XlsConverter, PptxConverter, ImageConverter, - WavConverter, - Mp3Converter, + AudioConverter, OutlookMsgConverter, ZipConverter, DocumentIntelligenceConverter, ) +from ._base_converter import DocumentConverter, DocumentConverterResult + from ._exceptions import ( FileConversionException, UnsupportedFormatException, FailedConversionAttempt, ) -# Override mimetype for csv to fix issue on windows -mimetypes.add_type("text/csv", ".csv") -_plugins: Union[None | List[Any]] = None +# Lower priority values are tried first. +PRIORITY_SPECIFIC_FILE_FORMAT = ( + 0.0 # e.g., .docx, .pdf, .xlsx, Or specific pages, e.g., wikipedia +) +PRIORITY_GENERIC_FILE_FORMAT = ( + 10.0 # Near catch-all converters for mimetypes like text/*, etc. +) -def _load_plugins() -> Union[None | List[Any]]: +_plugins: List[Any] = [] + + +def _load_plugins() -> List[Any]: """Lazy load plugins, exiting early if already loaded.""" global _plugins @@ -71,6 +81,14 @@ def _load_plugins() -> Union[None | List[Any]]: return _plugins +@dataclass(kw_only=True, frozen=True) +class ConverterRegistration: + """A registration of a converter with its priority and other metadata.""" + + converter: DocumentConverter + priority: float + + class MarkItDown: """(In preview) An extremely simple text-based document reader, suitable for LLM use. This reader will convert common file-types or webpages to Markdown.""" @@ -92,13 +110,13 @@ class MarkItDown: self._requests_session = requests_session # TODO - remove these (see enable_builtins) - self._llm_client = None - self._llm_model = None - self._exiftool_path = None - self._style_map = None + self._llm_client: Any = None + self._llm_model: Union[str | None] = None + self._exiftool_path: Union[str | None] = None + self._style_map: Union[str | None] = None # Register the converters - self._page_converters: List[DocumentConverter] = [] + self._converters: List[ConverterRegistration] = [] if ( enable_builtins is None or enable_builtins @@ -126,9 +144,15 @@ class MarkItDown: # Register converters for successful browsing operations # Later registrations are tried first / take higher priority than earlier registrations # To this end, the most specific converters should appear below the most generic converters - self.register_converter(PlainTextConverter()) - self.register_converter(ZipConverter()) - self.register_converter(HtmlConverter()) + self.register_converter( + PlainTextConverter(), priority=PRIORITY_GENERIC_FILE_FORMAT + ) + self.register_converter( + ZipConverter(markitdown=self), priority=PRIORITY_GENERIC_FILE_FORMAT + ) + self.register_converter( + HtmlConverter(), priority=PRIORITY_GENERIC_FILE_FORMAT + ) self.register_converter(RssConverter()) self.register_converter(WikipediaConverter()) self.register_converter(YouTubeConverter()) @@ -137,8 +161,7 @@ class MarkItDown: self.register_converter(XlsxConverter()) self.register_converter(XlsConverter()) self.register_converter(PptxConverter()) - self.register_converter(WavConverter()) - self.register_converter(Mp3Converter()) + self.register_converter(AudioConverter()) self.register_converter(ImageConverter()) self.register_converter(IpynbConverter()) self.register_converter(PdfConverter()) @@ -174,12 +197,17 @@ class MarkItDown: warn("Plugins converters are already enabled.", RuntimeWarning) def convert( - self, source: Union[str, requests.Response, Path], **kwargs: Any + self, + source: Union[str, requests.Response, Path, BinaryIO], + *, + stream_info: Optional[StreamInfo] = None, + **kwargs: Any, ) -> DocumentConverterResult: # TODO: deal with kwargs """ Args: - - source: can be a string representing a path either as string pathlib path object or url, or a requests.response object - - extension: specifies the file extension to use when interpreting the file. If None, infer from source (path, uri, content-type, etc.) + - source: can be a path (str or Path), url, or a requests.response object + - stream_info: optional stream info to use for the conversion. If None, infer from source + - kwargs: additional arguments to pass to the converter """ # Local path or url @@ -191,68 +219,120 @@ class MarkItDown: ): return self.convert_url(source, **kwargs) else: - return self.convert_local(source, **kwargs) + return self.convert_local(source, stream_info=stream_info, **kwargs) + # Path object + elif isinstance(source, Path): + return self.convert_local(source, stream_info=stream_info, **kwargs) # Request response elif isinstance(source, requests.Response): return self.convert_response(source, **kwargs) - elif isinstance(source, Path): - return self.convert_local(source, **kwargs) + # Binary stream + elif ( + hasattr(source, "read") + and callable(source.read) + and not isinstance(source, io.TextIOBase) + ): + return self.convert_stream(source, **kwargs) + else: + raise TypeError( + f"Invalid source type: {type(source)}. Expected str, requests.Response, BinaryIO." + ) def convert_local( - self, path: Union[str, Path], **kwargs: Any - ) -> DocumentConverterResult: # TODO: deal with kwargs + self, + path: Union[str, Path], + *, + stream_info: Optional[StreamInfo] = None, + file_extension: Optional[str] = None, # Deprecated -- use stream_info + url: Optional[str] = None, # Deprecated -- use stream_info + **kwargs: Any, + ) -> DocumentConverterResult: if isinstance(path, Path): path = str(path) - # Prepare a list of extensions to try (in order of priority) - ext = kwargs.get("file_extension") - extensions = [ext] if ext is not None else [] - # Get extension alternatives from the path and puremagic - base, ext = os.path.splitext(path) - self._append_ext(extensions, ext) + # Build a base StreamInfo object from which to start guesses + base_stream_info = StreamInfo( + local_path=path, + extension=os.path.splitext(path)[1], + filename=os.path.basename(path), + ) - for g in self._guess_ext_magic(path): - self._append_ext(extensions, g) + # Extend the base_stream_info with any additional info from the arguments + if stream_info is not None: + base_stream_info = base_stream_info.copy_and_update(stream_info) - # Convert - return self._convert(path, extensions, **kwargs) + if file_extension is not None: + # Deprecated -- use stream_info + base_stream_info = base_stream_info.copy_and_update( + extension=file_extension + ) + + if url is not None: + # Deprecated -- use stream_info + base_stream_info = base_stream_info.copy_and_update(url=url) + + with open(path, "rb") as fh: + # Prepare a list of configurations to try, starting with the base_stream_info + guesses: List[StreamInfo] = [base_stream_info] + for guess in _guess_stream_info_from_stream( + file_stream=fh, filename_hint=path + ): + guesses.append(base_stream_info.copy_and_update(guess)) + return self._convert(file_stream=fh, stream_info_guesses=guesses, **kwargs) - # TODO what should stream's type be? def convert_stream( - self, stream: Any, **kwargs: Any - ) -> DocumentConverterResult: # TODO: deal with kwargs - # Prepare a list of extensions to try (in order of priority) - ext = kwargs.get("file_extension") - extensions = [ext] if ext is not None else [] + self, + stream: BinaryIO, + *, + stream_info: Optional[StreamInfo] = None, + file_extension: Optional[str] = None, # Deprecated -- use stream_info + url: Optional[str] = None, # Deprecated -- use stream_info + **kwargs: Any, + ) -> DocumentConverterResult: + guesses: List[StreamInfo] = [] - # Save the file locally to a temporary file. It will be deleted before this method exits - handle, temp_path = tempfile.mkstemp() - fh = os.fdopen(handle, "wb") - result = None - try: - # Write to the temporary file - content = stream.read() - if isinstance(content, str): - fh.write(content.encode("utf-8")) + # Do we have anything on which to base a guess? + base_guess = None + if stream_info is not None or file_extension is not None or url is not None: + # Start with a non-Null base guess + if stream_info is None: + base_guess = StreamInfo() else: - fh.write(content) - fh.close() + base_guess = stream_info - # Use puremagic to check for more extension options - for g in self._guess_ext_magic(temp_path): - self._append_ext(extensions, g) + if file_extension is not None: + # Deprecated -- use stream_info + assert base_guess is not None # for mypy + base_guess = base_guess.copy_and_update(extension=file_extension) - # Convert - result = self._convert(temp_path, extensions, **kwargs) - # Clean up - finally: - try: - fh.close() - except Exception: - pass - os.unlink(temp_path) + if url is not None: + # Deprecated -- use stream_info + assert base_guess is not None # for mypy + base_guess = base_guess.copy_and_update(url=url) - return result + # Append the base guess, if it's non-trivial + if base_guess is not None: + if base_guess.mimetype is not None or base_guess.extension is not None: + guesses.append(base_guess) + else: + # Create a base guess with no information + base_guess = StreamInfo() + + # Create a placeholder filename to help with guessing + placeholder_filename = None + if base_guess.filename is not None: + placeholder_filename = base_guess.filename + elif base_guess.extension is not None: + placeholder_filename = "placeholder" + base_guess.extension + + # Add guesses based on stream content + for guess in _guess_stream_info_from_stream( + file_stream=stream, filename_hint=placeholder_filename + ): + guesses.append(base_guess.copy_and_update(guess)) + + # Perform the conversion + return self._convert(file_stream=stream, stream_info_guesses=guesses, **kwargs) def convert_url( self, url: str, **kwargs: Any @@ -263,55 +343,94 @@ class MarkItDown: return self.convert_response(response, **kwargs) def convert_response( - self, response: requests.Response, **kwargs: Any - ) -> DocumentConverterResult: # TODO fix kwargs type - # Prepare a list of extensions to try (in order of priority) - ext = kwargs.get("file_extension") - extensions = [ext] if ext is not None else [] + self, + response: requests.Response, + *, + stream_info: Optional[StreamInfo] = None, + file_extension: Optional[str] = None, # Deprecated -- use stream_info + url: Optional[str] = None, # Deprecated -- use stream_info + **kwargs: Any, + ) -> DocumentConverterResult: + # If there is a content-type header, get the mimetype and charset (if present) + mimetype: Optional[str] = None + charset: Optional[str] = None - # Guess from the mimetype - content_type = response.headers.get("content-type", "").split(";")[0] - self._append_ext(extensions, mimetypes.guess_extension(content_type)) + if "content-type" in response.headers: + parts = response.headers["content-type"].split(";") + mimetype = parts.pop(0).strip() + for part in parts: + if part.strip().startswith("charset="): + _charset = part.split("=")[1].strip() + if len(_charset) > 0: + charset = _charset - # Read the content disposition if there is one - content_disposition = response.headers.get("content-disposition", "") - m = re.search(r"filename=([^;]+)", content_disposition) - if m: - base, ext = os.path.splitext(m.group(1).strip("\"'")) - self._append_ext(extensions, ext) + # If there is a content-disposition header, get the filename and possibly the extension + filename: Optional[str] = None + extension: Optional[str] = None + if "content-disposition" in response.headers: + m = re.search(r"filename=([^;]+)", response.headers["content-disposition"]) + if m: + filename = m.group(1).strip("\"'") + _, _extension = os.path.splitext(filename) + if len(_extension) > 0: + extension = _extension - # Read from the extension from the path - base, ext = os.path.splitext(urlparse(response.url).path) - self._append_ext(extensions, ext) + # If there is still no filename, try to read it from the url + if filename is None: + parsed_url = urlparse(response.url) + _, _extension = os.path.splitext(parsed_url.path) + if len(_extension) > 0: # Looks like this might be a file! + filename = os.path.basename(parsed_url.path) + extension = _extension - # Save the file locally to a temporary file. It will be deleted before this method exits - handle, temp_path = tempfile.mkstemp() - fh = os.fdopen(handle, "wb") - result = None - try: - # Download the file - for chunk in response.iter_content(chunk_size=512): - fh.write(chunk) - fh.close() + # Create an initial guess from all this information + base_guess = StreamInfo( + mimetype=mimetype, + charset=charset, + filename=filename, + extension=extension, + url=response.url, + ) - # Use puremagic to check for more extension options - for g in self._guess_ext_magic(temp_path): - self._append_ext(extensions, g) + # Update with any additional info from the arguments + if stream_info is not None: + base_guess = base_guess.copy_and_update(stream_info) + if file_extension is not None: + # Deprecated -- use stream_info + base_guess = base_guess.copy_and_update(extension=file_extension) + if url is not None: + # Deprecated -- use stream_info + base_guess = base_guess.copy_and_update(url=url) - # Convert - result = self._convert(temp_path, extensions, url=response.url, **kwargs) - # Clean up - finally: - try: - fh.close() - except Exception: - pass - os.unlink(temp_path) + # Add the guess if its non-trivial + guesses: List[StreamInfo] = [] + if base_guess.mimetype is not None or base_guess.extension is not None: + guesses.append(base_guess) - return result + # Read into BytesIO + buffer = io.BytesIO() + for chunk in response.iter_content(chunk_size=512): + buffer.write(chunk) + buffer.seek(0) + + # Create a placeholder filename to help with guessing + placeholder_filename = None + if base_guess.filename is not None: + placeholder_filename = base_guess.filename + elif base_guess.extension is not None: + placeholder_filename = "placeholder" + base_guess.extension + + # Add guesses based on stream content + for guess in _guess_stream_info_from_stream( + file_stream=buffer, filename_hint=placeholder_filename + ): + guesses.append(base_guess.copy_and_update(guess)) + + # Convert + return self._convert(file_stream=buffer, stream_info_guesses=guesses, **kwargs) def _convert( - self, local_path: str, extensions: List[Union[str, None]], **kwargs + self, *, file_stream: BinaryIO, stream_info_guesses: List[StreamInfo], **kwargs ) -> DocumentConverterResult: res: Union[None, DocumentConverterResult] = None @@ -321,19 +440,21 @@ class MarkItDown: # Create a copy of the page_converters list, sorted by priority. # We do this with each call to _convert because the priority of converters may change between calls. # The sort is guaranteed to be stable, so converters with the same priority will remain in the same order. - sorted_converters = sorted(self._page_converters, key=lambda x: x.priority) + sorted_registrations = sorted(self._converters, key=lambda x: x.priority) + + # Remember the initial stream position so that we can return to it + cur_pos = file_stream.tell() + + for stream_info in stream_info_guesses + [StreamInfo()]: + for converter_registration in sorted_registrations: + converter = converter_registration.converter + # Sanity check -- make sure the cur_pos is still the same + assert ( + cur_pos == file_stream.tell() + ), f"File stream position should NOT change between guess iterations" - for ext in extensions + [None]: # Try last with no extension - for converter in sorted_converters: _kwargs = copy.deepcopy(kwargs) - # Overwrite file_extension appropriately - if ext is None: - if "file_extension" in _kwargs: - del _kwargs["file_extension"] - else: - _kwargs.update({"file_extension": ext}) - # Copy any additional global options if "llm_client" not in _kwargs and self._llm_client is not None: _kwargs["llm_client"] = self._llm_client @@ -348,17 +469,40 @@ class MarkItDown: _kwargs["exiftool_path"] = self._exiftool_path # Add the list of converters for nested processing - _kwargs["_parent_converters"] = self._page_converters + _kwargs["_parent_converters"] = self._converters - # If we hit an error log it and keep trying + # Add legaxy kwargs + if stream_info is not None: + if stream_info.extension is not None: + _kwargs["file_extension"] = stream_info.extension + + if stream_info.url is not None: + _kwargs["url"] = stream_info.url + + # Check if the converter will accept the file, and if so, try to convert it + _accepts = False try: - res = converter.convert(local_path, **_kwargs) - except Exception: - failed_attempts.append( - FailedConversionAttempt( - converter=converter, exc_info=sys.exc_info() + _accepts = converter.accepts(file_stream, stream_info, **_kwargs) + except NotImplementedError: + pass + + # accept() should not have changed the file stream position + assert ( + cur_pos == file_stream.tell() + ), f"{type(converter).__name__}.accept() should NOT change the file_stream position" + + # Attempt the conversion + if _accepts: + try: + res = converter.convert(file_stream, stream_info, **_kwargs) + except Exception: + failed_attempts.append( + FailedConversionAttempt( + converter=converter, exc_info=sys.exc_info() + ) ) - ) + finally: + file_stream.seek(cur_pos) if res is not None: # Normalize the content @@ -366,8 +510,6 @@ class MarkItDown: [line.rstrip() for line in re.split(r"\r?\n", res.text_content)] ) res.text_content = re.sub(r"\n{3,}", "\n\n", res.text_content) - - # Todo return res # If we got this far without success, report any exceptions @@ -376,61 +518,9 @@ class MarkItDown: # Nothing can handle it! raise UnsupportedFormatException( - f"Could not convert '{local_path}' to Markdown. No converter attempted a conversion, suggesting that the filetype is simply not supported." + f"Could not convert stream to Markdown. No converter attempted a conversion, suggesting that the filetype is simply not supported." ) - def _append_ext(self, extensions, ext): - """Append a unique non-None, non-empty extension to a list of extensions.""" - if ext is None: - return - ext = ext.strip() - if ext == "": - return - if ext in extensions: - return - extensions.append(ext) - - def _guess_ext_magic(self, path): - """Use puremagic (a Python implementation of libmagic) to guess a file's extension based on the first few bytes.""" - # Use puremagic to guess - try: - guesses = puremagic.magic_file(path) - - # Fix for: https://github.com/microsoft/markitdown/issues/222 - # If there are no guesses, then try again after trimming leading ASCII whitespaces. - # ASCII whitespace characters are those byte values in the sequence b' \t\n\r\x0b\f' - # (space, tab, newline, carriage return, vertical tab, form feed). - if len(guesses) == 0: - with open(path, "rb") as file: - while True: - char = file.read(1) - if not char: # End of file - break - if not char.isspace(): - file.seek(file.tell() - 1) - break - try: - guesses = puremagic.magic_stream(file) - except puremagic.main.PureError: - pass - - extensions = list() - for g in guesses: - ext = g.extension.strip() - if len(ext) > 0: - if not ext.startswith("."): - ext = "." + ext - if ext not in extensions: - extensions.append(ext) - return extensions - except FileNotFoundError: - pass - except IsADirectoryError: - pass - except PermissionError: - pass - return [] - def register_page_converter(self, converter: DocumentConverter) -> None: """DEPRECATED: User register_converter instead.""" warn( @@ -439,6 +529,34 @@ class MarkItDown: ) self.register_converter(converter) - def register_converter(self, converter: DocumentConverter) -> None: - """Register a page text converter.""" - self._page_converters.insert(0, converter) + def register_converter( + self, + converter: DocumentConverter, + *, + priority: float = PRIORITY_SPECIFIC_FILE_FORMAT, + ) -> None: + """ + Register a DocumentConverter with a given priority. + + Priorities work as follows: By default, most converters get priority + DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT (== 0). The exception + is the PlainTextConverter, HtmlConverter, and ZipConverter, which get + priority PRIORITY_SPECIFIC_FILE_FORMAT (== 10), with lower values + being tried first (i.e., higher priority). + + Just prior to conversion, the converters are sorted by priority, using + a stable sort. This means that converters with the same priority will + remain in the same order, with the most recently registered converters + appearing first. + + We have tight control over the order of built-in converters, but + plugins can register converters in any order. The registration's priority + field reasserts some control over the order of converters. + + Plugins can register converters with any priority, to appear before or + after the built-ins. For example, a plugin with priority 9 will run + before the PlainTextConverter, but after the built-in converters. + """ + self._converters.insert( + 0, ConverterRegistration(converter=converter, priority=priority) + ) diff --git a/packages/markitdown/src/markitdown/_stream_info.py b/packages/markitdown/src/markitdown/_stream_info.py new file mode 100644 index 0000000..1eaa4d2 --- /dev/null +++ b/packages/markitdown/src/markitdown/_stream_info.py @@ -0,0 +1,122 @@ +import puremagic +import mimetypes +import os +from dataclasses import dataclass, asdict +from typing import Optional, BinaryIO, List, TypeVar, Type + +# Mimetype substitutions table +MIMETYPE_SUBSTITUTIONS = { + "application/excel": "application/vnd.ms-excel", + "application/mspowerpoint": "application/vnd.ms-powerpoint", +} + + +@dataclass(kw_only=True, frozen=True) +class StreamInfo: + """The StreamInfo class is used to store information about a file stream. + All fields can be None, and will depend on how the stream was opened. + """ + + mimetype: Optional[str] = None + extension: Optional[str] = None + charset: Optional[str] = None + filename: Optional[ + str + ] = None # From local path, url, or Content-Disposition header + local_path: Optional[str] = None # If read from disk + url: Optional[str] = None # If read from url + + def copy_and_update(self, *args, **kwargs): + """Copy the StreamInfo object and update it with the given StreamInfo + instance and/or other keyword arguments.""" + new_info = asdict(self) + + for si in args: + assert isinstance(si, StreamInfo) + new_info.update({k: v for k, v in asdict(si).items() if v is not None}) + + if len(kwargs) > 0: + new_info.update(kwargs) + + return StreamInfo(**new_info) + + +# Behavior subject to change. +# Do not rely on this outside of this module. +def _guess_stream_info_from_stream( + file_stream: BinaryIO, + *, + filename_hint: Optional[str] = None, +) -> List[StreamInfo]: + """ + Guess StreamInfo properties (mostly mimetype and extension) from a stream. + + Args: + - stream: The stream to guess the StreamInfo from. + - filename_hint [Optional]: A filename hint to help with the guessing (may be a placeholder, and not actually be the file name) + + Returns a list of StreamInfo objects in order of confidence. + """ + guesses: List[StreamInfo] = [] + + # Add a guess purely based on the filename hint + if filename_hint: + try: + # Requires Python 3.13+ + mimetype, _ = mimetypes.guess_file_type(filename_hint) # type: ignore + except AttributeError: + mimetype, _ = mimetypes.guess_type(filename_hint) + + if mimetype: + guesses.append( + StreamInfo( + mimetype=mimetype, extension=os.path.splitext(filename_hint)[1] + ) + ) + + def _puremagic( + file_stream, filename_hint + ) -> List[puremagic.main.PureMagicWithConfidence]: + """Wrap guesses to handle exceptions.""" + try: + return puremagic.magic_stream(file_stream, filename=filename_hint) + except puremagic.main.PureError as e: + return [] + + cur_pos = file_stream.tell() + type_guesses = _puremagic(file_stream, filename_hint=filename_hint) + if len(type_guesses) == 0: + # Fix for: https://github.com/microsoft/markitdown/issues/222 + # If there are no guesses, then try again after trimming leading ASCII whitespaces. + # ASCII whitespace characters are those byte values in the sequence b' \t\n\r\x0b\f' + # (space, tab, newline, carriage return, vertical tab, form feed). + + # Eat all the leading whitespace + file_stream.seek(cur_pos) + while True: + char = file_stream.read(1) + if not char: # End of file + break + if not char.isspace(): + file_stream.seek(file_stream.tell() - 1) + break + + # Try again + type_guesses = _puremagic(file_stream, filename_hint=filename_hint) + file_stream.seek(cur_pos) + + # Convert and return the guesses + for guess in type_guesses: + kwargs: dict[str, str] = {} + if guess.extension: + kwargs["extension"] = guess.extension + if guess.mime_type: + kwargs["mimetype"] = MIMETYPE_SUBSTITUTIONS.get( + guess.mime_type, guess.mime_type + ) + if len(kwargs) > 0: + # We don't add the filename_hint, because sometimes it's just a placeholder, + # and, in any case, doesn't add new information. + guesses.append(StreamInfo(**kwargs)) + + return guesses diff --git a/packages/markitdown/src/markitdown/converters/__init__.py b/packages/markitdown/src/markitdown/converters/__init__.py index 1e5afe4..f43efe3 100644 --- a/packages/markitdown/src/markitdown/converters/__init__.py +++ b/packages/markitdown/src/markitdown/converters/__init__.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT -from ._base import DocumentConverter, DocumentConverterResult from ._plain_text_converter import PlainTextConverter from ._html_converter import HtmlConverter from ._rss_converter import RssConverter @@ -15,15 +14,12 @@ from ._docx_converter import DocxConverter from ._xlsx_converter import XlsxConverter, XlsConverter from ._pptx_converter import PptxConverter from ._image_converter import ImageConverter -from ._wav_converter import WavConverter -from ._mp3_converter import Mp3Converter +from ._audio_converter import AudioConverter from ._outlook_msg_converter import OutlookMsgConverter from ._zip_converter import ZipConverter from ._doc_intel_converter import DocumentIntelligenceConverter __all__ = [ - "DocumentConverter", - "DocumentConverterResult", "PlainTextConverter", "HtmlConverter", "RssConverter", @@ -37,8 +33,7 @@ __all__ = [ "XlsConverter", "PptxConverter", "ImageConverter", - "WavConverter", - "Mp3Converter", + "AudioConverter", "OutlookMsgConverter", "ZipConverter", "DocumentIntelligenceConverter", diff --git a/packages/markitdown/src/markitdown/converters/_audio_converter.py b/packages/markitdown/src/markitdown/converters/_audio_converter.py new file mode 100644 index 0000000..845ad5d --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_audio_converter.py @@ -0,0 +1,102 @@ +import io +from typing import Any, BinaryIO, Optional + +from ._exiftool import exiftool_metadata +from ._transcribe_audio import transcribe_audio +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo +from .._exceptions import MissingDependencyException + +ACCEPTED_MIME_TYPE_PREFIXES = [ + "audio/x-wav", + "audio/mpeg", + "video/mp4", +] + +ACCEPTED_FILE_EXTENSIONS = [ + ".wav", + ".mp3", + ".m4a", + ".mp4", +] + + +class AudioConverter(DocumentConverter): + """ + Converts audio files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` is installed). + """ + + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + md_content = "" + + # Add metadata + metadata = exiftool_metadata( + file_stream, exiftool_path=kwargs.get("exiftool_path") + ) + if metadata: + for f in [ + "Title", + "Artist", + "Author", + "Band", + "Album", + "Genre", + "Track", + "DateTimeOriginal", + "CreateDate", + # "Duration", -- Wrong values when read from memory + "NumChannels", + "SampleRate", + "AvgBytesPerSec", + "BitsPerSample", + ]: + if f in metadata: + md_content += f"{f}: {metadata[f]}\n" + + # Figure out the audio format for transcription + if stream_info.extension == ".wav" or stream_info.mimetype == "audio/x-wav": + audio_format = "wav" + elif stream_info.extension == ".mp3" or stream_info.mimetype == "audio/mpeg": + audio_format = "mp3" + elif ( + stream_info.extension in [".mp4", ".m4a"] + or stream_info.mimetype == "video/mp4" + ): + audio_format = "mp4" + else: + audio_format = None + + # Transcribe + if audio_format: + try: + transcript = transcribe_audio(file_stream, audio_format=audio_format) + if transcript: + md_content += "\n\n### Audio Transcript:\n" + transcript + except MissingDependencyException: + pass + + # Return the result + return DocumentConverterResult(markdown=md_content.strip()) diff --git a/packages/markitdown/src/markitdown/converters/_base.py b/packages/markitdown/src/markitdown/converters/_base.py deleted file mode 100644 index 0f351fc..0000000 --- a/packages/markitdown/src/markitdown/converters/_base.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import Any, Union - - -class DocumentConverterResult: - """The result of converting a document to text.""" - - def __init__(self, title: Union[str, None] = None, text_content: str = ""): - self.title: Union[str, None] = title - self.text_content: str = text_content - - -class DocumentConverter: - """Abstract superclass of all DocumentConverters.""" - - # Lower priority values are tried first. - PRIORITY_SPECIFIC_FILE_FORMAT = ( - 0.0 # e.g., .docx, .pdf, .xlsx, Or specific pages, e.g., wikipedia - ) - PRIORITY_GENERIC_FILE_FORMAT = ( - 10.0 # Near catch-all converters for mimetypes like text/*, etc. - ) - - def __init__(self, priority: float = PRIORITY_SPECIFIC_FILE_FORMAT): - """ - Initialize the DocumentConverter with a given priority. - - Priorities work as follows: By default, most converters get priority - DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT (== 0). The exception - is the PlainTextConverter, which gets priority PRIORITY_SPECIFIC_FILE_FORMAT (== 10), - with lower values being tried first (i.e., higher priority). - - Just prior to conversion, the converters are sorted by priority, using - a stable sort. This means that converters with the same priority will - remain in the same order, with the most recently registered converters - appearing first. - - We have tight control over the order of built-in converters, but - plugins can register converters in any order. A converter's priority - field reasserts some control over the order of converters. - - Plugins can register converters with any priority, to appear before or - after the built-ins. For example, a plugin with priority 9 will run - before the PlainTextConverter, but after the built-in converters. - """ - self._priority = priority - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - raise NotImplementedError("Subclasses must implement this method") - - @property - def priority(self) -> float: - """Priority of the converter in markitdown's converter list. Higher priority values are tried first.""" - return self._priority - - @priority.setter - def priority(self, value: float): - self._priority = value - - @priority.deleter - def priority(self): - raise AttributeError("Cannot delete the priority attribute") diff --git a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py index d1b11a6..7dd9e24 100644 --- a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py +++ b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py @@ -1,14 +1,24 @@ -# type: ignore -import base64 +import io import re - -from typing import Union +import base64 from urllib.parse import parse_qs, urlparse +from typing import Any, BinaryIO, Optional from bs4 import BeautifulSoup -from ._base import DocumentConverter, DocumentConverterResult +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo from ._markdownify import _CustomMarkdownify +ACCEPTED_MIME_TYPE_PREFIXES = [ + "text/html", + "application/xhtml", +] + +ACCEPTED_FILE_EXTENSIONS = [ + ".html", + ".htm", +] + class BingSerpConverter(DocumentConverter): """ @@ -16,28 +26,47 @@ class BingSerpConverter(DocumentConverter): NOTE: It is better to use the Bing API """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + """ + Make sure we're dealing with HTML content *from* Bing. + """ + + url = stream_info.url or "" + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a Bing SERP - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - url = kwargs.get("url", "") if not re.search(r"^https://www\.bing\.com/search\?q=", url): - return None + # Not a Bing SERP URL + return False + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + # Not HTML content + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Parse the query parameters - parsed_params = parse_qs(urlparse(url).query) + parsed_params = parse_qs(urlparse(stream_info.url).query) query = parsed_params.get("q", [""])[0] - # Parse the file - soup = None - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") + # Parse the stream + encoding = "utf-8" if stream_info.charset is None else stream_info.charset + soup = BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) # Clean up some formatting for tptt in soup.find_all(class_="tptt"): @@ -81,6 +110,6 @@ class BingSerpConverter(DocumentConverter): ) return DocumentConverterResult( + markdown=webpage_text, title=None if soup.title is None else soup.title.string, - text_content=webpage_text, ) diff --git a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py index 6fe79c0..2f116d0 100644 --- a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py +++ b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py @@ -1,9 +1,12 @@ -from typing import Any, Union -import re import sys +import re -from ._base import DocumentConverter, DocumentConverterResult -from .._exceptions import MissingDependencyException +from typing import BinaryIO, Any, List + +from ._html_converter import HtmlConverter +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo +from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE # Try loading optional (but in this case, required) dependencies # Save reporting of any exceptions for later @@ -26,17 +29,50 @@ except ImportError: CONTENT_FORMAT = "markdown" +OFFICE_MIME_TYPE_PREFIXES = [ + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.presentationml", + "application/xhtml", + "text/html", +] + +OTHER_MIME_TYPE_PREFIXES = [ + "application/pdf", + "application/x-pdf", + "text/html", + "image/", +] + +OFFICE_FILE_EXTENSIONS = [ + ".docx", + ".xlsx", + ".pptx", + ".html", + ".htm", +] + +OTHER_FILE_EXTENSIONS = [ + ".pdf", + ".jpeg", + ".jpg", + ".png", + ".bmp", + ".tiff", + ".heif", +] + + class DocumentIntelligenceConverter(DocumentConverter): """Specialized DocumentConverter that uses Document Intelligence to extract text from documents.""" def __init__( self, *, - priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT, endpoint: str, api_version: str = "2024-07-31-preview", ): - super().__init__(priority=priority) + super().__init__() # Raise an error if the dependencies are not available. # This is different than other converters since this one isn't even instantiated @@ -44,9 +80,11 @@ class DocumentIntelligenceConverter(DocumentConverter): if _dependency_exc_info is not None: raise MissingDependencyException( "DocumentIntelligenceConverter requires the optional dependency [az-doc-intel] (or [all]) to be installed. E.g., `pip install markitdown[az-doc-intel]`" - ) from _dependency_exc_info[1].with_traceback( + ) from _dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _dependency_exc_info[2] - ) # Restore the original traceback + ) self.endpoint = endpoint self.api_version = api_version @@ -55,55 +93,62 @@ class DocumentIntelligenceConverter(DocumentConverter): api_version=self.api_version, credential=DefaultAzureCredential(), ) - self._priority = priority + + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in OFFICE_FILE_EXTENSIONS + OTHER_FILE_EXTENSIONS: + return True + + for prefix in OFFICE_MIME_TYPE_PREFIXES + OTHER_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def _analysis_features(self, stream_info: StreamInfo) -> List[str]: + """ + Helper needed to determine which analysis features to use. + Certain document analysis features are not availiable for + office filetypes (.xlsx, .pptx, .html, .docx) + """ + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in OFFICE_FILE_EXTENSIONS: + return [] + + for prefix in OFFICE_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return [] + + return [ + DocumentAnalysisFeature.FORMULAS, # enable formula extraction + DocumentAnalysisFeature.OCR_HIGH_RESOLUTION, # enable high resolution OCR + DocumentAnalysisFeature.STYLE_FONT, # enable font style extraction + ] def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if extension is not supported by Document Intelligence - extension = kwargs.get("file_extension", "") - docintel_extensions = [ - ".pdf", - ".docx", - ".xlsx", - ".pptx", - ".html", - ".jpeg", - ".jpg", - ".png", - ".bmp", - ".tiff", - ".heif", - ] - if extension.lower() not in docintel_extensions: - return None - - # Get the bytestring for the local path - with open(local_path, "rb") as f: - file_bytes = f.read() - - # Certain document analysis features are not availiable for office filetypes (.xlsx, .pptx, .html, .docx) - if extension.lower() in [".xlsx", ".pptx", ".html", ".docx"]: - analysis_features = [] - else: - analysis_features = [ - DocumentAnalysisFeature.FORMULAS, # enable formula extraction - DocumentAnalysisFeature.OCR_HIGH_RESOLUTION, # enable high resolution OCR - DocumentAnalysisFeature.STYLE_FONT, # enable font style extraction - ] - + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Extract the text using Azure Document Intelligence poller = self.doc_intel_client.begin_analyze_document( model_id="prebuilt-layout", - body=AnalyzeDocumentRequest(bytes_source=file_bytes), - features=analysis_features, + body=AnalyzeDocumentRequest(bytes_source=file_stream.read()), + features=self._analysis_features(stream_info), output_content_format=CONTENT_FORMAT, # TODO: replace with "ContentFormat.MARKDOWN" when the bug is fixed ) result: AnalyzeResult = poller.result() # remove comments from the markdown content generated by Doc Intelligence and append to markdown string markdown_text = re.sub(r"", "", result.content, flags=re.DOTALL) - return DocumentConverterResult( - title=None, - text_content=markdown_text, - ) + return DocumentConverterResult(markdown=markdown_text) diff --git a/packages/markitdown/src/markitdown/converters/_docx_converter.py b/packages/markitdown/src/markitdown/converters/_docx_converter.py index 0866e59..c568acb 100644 --- a/packages/markitdown/src/markitdown/converters/_docx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_docx_converter.py @@ -1,13 +1,10 @@ import sys -from typing import Union +from typing import BinaryIO, Any -from ._base import ( - DocumentConverterResult, -) - -from ._base import DocumentConverter from ._html_converter import HtmlConverter +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE # Try loading optional (but in this case, required) dependencies @@ -20,22 +17,46 @@ except ImportError: _dependency_exc_info = sys.exc_info() +ACCEPTED_MIME_TYPE_PREFIXES = [ + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", +] + +ACCEPTED_FILE_EXTENSIONS = [".docx"] + + class DocxConverter(HtmlConverter): """ Converts DOCX files to Markdown. Style information (e.g.m headings) and tables are preserved where possible. """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def __init__(self): + super().__init__() + self._html_converter = HtmlConverter() - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a DOCX - extension = kwargs.get("file_extension", "") - if extension.lower() != ".docx": - return None + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Check: the dependencies if _dependency_exc_info is not None: raise MissingDependencyException( @@ -44,16 +65,13 @@ class DocxConverter(HtmlConverter): extension=".docx", feature="docx", ) - ) from _dependency_exc_info[1].with_traceback( + ) from _dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _dependency_exc_info[2] - ) # Restore the original traceback + ) - result = None - with open(local_path, "rb") as docx_file: - style_map = kwargs.get("style_map", None) - - result = mammoth.convert_to_html(docx_file, style_map=style_map) - html_content = result.value - result = self._convert(html_content) - - return result + style_map = kwargs.get("style_map", None) + return self._html_converter.convert_string( + mammoth.convert_to_html(file_stream, style_map=style_map).value + ) diff --git a/packages/markitdown/src/markitdown/converters/_exiftool.py b/packages/markitdown/src/markitdown/converters/_exiftool.py new file mode 100644 index 0000000..5a316f0 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_exiftool.py @@ -0,0 +1,44 @@ +import json +import subprocess +import locale +import sys +import shutil +import os +import warnings +from typing import BinaryIO, Optional, Any + + +def exiftool_metadata( + file_stream: BinaryIO, *, exiftool_path: Optional[str] = None +) -> Any: # Need a better type for json data + # Check if we have a valid pointer to exiftool + if not exiftool_path: + which_exiftool = shutil.which("exiftool") + if which_exiftool: + warnings.warn( + f"""Implicit discovery of 'exiftool' is disabled. If you would like to continue to use exiftool in MarkItDown, please set the exiftool_path parameter in the MarkItDown consructor. E.g., + + md = MarkItDown(exiftool_path="{which_exiftool}") + +This warning will be removed in future releases. +""", + DeprecationWarning, + ) + # Nothing to do + return {} + + # Run exiftool + cur_pos = file_stream.tell() + try: + output = subprocess.run( + [exiftool_path, "-json", "-"], + input=file_stream.read(), + capture_output=True, + text=False, + ).stdout + + return json.loads( + output.decode(locale.getpreferredencoding(False)), + )[0] + finally: + file_stream.seek(cur_pos) diff --git a/packages/markitdown/src/markitdown/converters/_html_converter.py b/packages/markitdown/src/markitdown/converters/_html_converter.py index 68c2536..8a8203d 100644 --- a/packages/markitdown/src/markitdown/converters/_html_converter.py +++ b/packages/markitdown/src/markitdown/converters/_html_converter.py @@ -1,37 +1,52 @@ -from typing import Any, Union +import io +from typing import Any, BinaryIO, Optional from bs4 import BeautifulSoup -from ._base import DocumentConverter, DocumentConverterResult +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo from ._markdownify import _CustomMarkdownify +ACCEPTED_MIME_TYPE_PREFIXES = [ + "text/html", + "application/xhtml", +] + +ACCEPTED_FILE_EXTENSIONS = [ + ".html", + ".htm", +] + class HtmlConverter(DocumentConverter): """Anything with content type text/html""" - def __init__( - self, priority: float = DocumentConverter.PRIORITY_GENERIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not html - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - - result = None - with open(local_path, "rt", encoding="utf-8") as fh: - result = self._convert(fh.read()) - - return result - - def _convert(self, html_content: str) -> Union[None, DocumentConverterResult]: - """Helper function that converts an HTML string.""" - - # Parse the string - soup = BeautifulSoup(html_content, "html.parser") + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + # Parse the stream + encoding = "utf-8" if stream_info.charset is None else stream_info.charset + soup = BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) # Remove javascript and style blocks for script in soup(["script", "style"]): @@ -51,6 +66,25 @@ class HtmlConverter(DocumentConverter): webpage_text = webpage_text.strip() return DocumentConverterResult( + markdown=webpage_text, title=None if soup.title is None else soup.title.string, - text_content=webpage_text, + ) + + def convert_string( + self, html_content: str, *, url: Optional[str] = None, **kwargs + ) -> DocumentConverterResult: + """ + Non-standard convenience method to convert a string to markdown. + Given that many converters produce HTML as intermediate output, this + allows for easy conversion of HTML to markdown. + """ + return self.convert( + file_stream=io.BytesIO(html_content.encode("utf-8")), + stream_info=StreamInfo( + mimetype="text/html", + extension=".html", + charset="utf-8", + url=url, + ), + **kwargs, ) diff --git a/packages/markitdown/src/markitdown/converters/_image_converter.py b/packages/markitdown/src/markitdown/converters/_image_converter.py index 4eb6155..dd8fbac 100644 --- a/packages/markitdown/src/markitdown/converters/_image_converter.py +++ b/packages/markitdown/src/markitdown/converters/_image_converter.py @@ -1,30 +1,53 @@ -from typing import Union -from ._base import DocumentConverter, DocumentConverterResult -from ._media_converter import MediaConverter +from typing import BinaryIO, Any, Union import base64 import mimetypes +from ._exiftool import exiftool_metadata +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo + +ACCEPTED_MIME_TYPE_PREFIXES = [ + "image/jpeg", + "image/png", +] + +ACCEPTED_FILE_EXTENSIONS = [".jpg", ".jpeg", ".png"] -class ImageConverter(MediaConverter): +class ImageConverter(DocumentConverter): """ Converts images to markdown via extraction of metadata (if `exiftool` is installed), and description via a multimodal LLM (if an llm_client is configured). """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not an image - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".jpg", ".jpeg", ".png"]: - return None + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: md_content = "" # Add metadata - metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) + metadata = exiftool_metadata( + file_stream, exiftool_path=kwargs.get("exiftool_path") + ) if metadata: for f in [ @@ -42,39 +65,59 @@ class ImageConverter(MediaConverter): if f in metadata: md_content += f"{f}: {metadata[f]}\n" - # Try describing the image with GPTV + # Try describing the image with GPT llm_client = kwargs.get("llm_client") llm_model = kwargs.get("llm_model") if llm_client is not None and llm_model is not None: - md_content += ( - "\n# Description:\n" - + self._get_llm_description( - local_path, - extension, - llm_client, - llm_model, - prompt=kwargs.get("llm_prompt"), - ).strip() - + "\n" + llm_description = self._get_llm_description( + file_stream, + stream_info, + client=llm_client, + model=llm_model, + prompt=kwargs.get("llm_prompt"), ) + if llm_description is not None: + md_content += "\n# Description:\n" + llm_description.strip() + "\n" + return DocumentConverterResult( - title=None, - text_content=md_content, + markdown=md_content, ) - def _get_llm_description(self, local_path, extension, client, model, prompt=None): + def _get_llm_description( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + *, + client, + model, + prompt=None, + ) -> Union[None, str]: if prompt is None or prompt.strip() == "": prompt = "Write a detailed caption for this image." - data_uri = "" - with open(local_path, "rb") as image_file: - content_type, encoding = mimetypes.guess_type("_dummy" + extension) - if content_type is None: - content_type = "image/jpeg" - image_base64 = base64.b64encode(image_file.read()).decode("utf-8") - data_uri = f"data:{content_type};base64,{image_base64}" + # Get the content type + content_type = stream_info.mimetype + if not content_type: + content_type, _ = mimetypes.guess_type( + "_dummy" + (stream_info.extension or "") + ) + if not content_type: + content_type = "application/octet-stream" + # Convert to base64 + cur_pos = file_stream.tell() + try: + base64_image = base64.b64encode(file_stream.read()).decode("utf-8") + except Exception as e: + return None + finally: + file_stream.seek(cur_pos) + + # Prepare the data-uri + data_uri = f"data:{content_type};base64,{base64_image}" + + # Prepare the OpenAI API request messages = [ { "role": "user", @@ -90,5 +133,6 @@ class ImageConverter(MediaConverter): } ] + # Call the OpenAI API response = client.chat.completions.create(model=model, messages=messages) return response.choices[0].message.content diff --git a/packages/markitdown/src/markitdown/converters/_ipynb_converter.py b/packages/markitdown/src/markitdown/converters/_ipynb_converter.py index b487f41..f8ba193 100644 --- a/packages/markitdown/src/markitdown/converters/_ipynb_converter.py +++ b/packages/markitdown/src/markitdown/converters/_ipynb_converter.py @@ -1,39 +1,62 @@ +from typing import BinaryIO, Any import json -from typing import Any, Union - -from ._base import ( - DocumentConverter, - DocumentConverterResult, -) +from .._base_converter import DocumentConverter, DocumentConverterResult from .._exceptions import FileConversionException +from .._stream_info import StreamInfo + +CANDIDATE_MIME_TYPE_PREFIXES = [ + "application/json", +] + +ACCEPTED_FILE_EXTENSIONS = [".ipynb"] class IpynbConverter(DocumentConverter): """Converts Jupyter Notebook (.ipynb) files to Markdown.""" - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in CANDIDATE_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + # Read further to see if it's a notebook + cur_pos = file_stream.tell() + try: + encoding = stream_info.charset or "utf-8" + notebook_content = file_stream.read().decode(encoding) + return ( + "nbformat" in notebook_content + and "nbformat_minor" in notebook_content + ) + finally: + file_stream.seek(cur_pos) + + return False def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not ipynb - extension = kwargs.get("file_extension", "") - if extension.lower() != ".ipynb": - return None - + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Parse and convert the notebook result = None - with open(local_path, "rt", encoding="utf-8") as fh: - notebook_content = json.load(fh) - result = self._convert(notebook_content) - return result + encoding = stream_info.charset or "utf-8" + notebook_content = file_stream.read().decode(encoding=encoding) + return self._convert(json.loads(notebook_content)) - def _convert(self, notebook_content: dict) -> Union[None, DocumentConverterResult]: + def _convert(self, notebook_content: dict) -> DocumentConverterResult: """Helper function that converts notebook JSON content to Markdown.""" try: md_output = [] @@ -65,8 +88,8 @@ class IpynbConverter(DocumentConverter): title = notebook_content.get("metadata", {}).get("title", title) return DocumentConverterResult( + markdown=md_text, title=title, - text_content=md_text, ) except Exception as e: diff --git a/packages/markitdown/src/markitdown/converters/_llm_caption.py b/packages/markitdown/src/markitdown/converters/_llm_caption.py new file mode 100644 index 0000000..b851dc8 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_llm_caption.py @@ -0,0 +1,50 @@ +from typing import BinaryIO, Any, Union +import base64 +import mimetypes +from .._stream_info import StreamInfo + + +def llm_caption( + file_stream: BinaryIO, stream_info: StreamInfo, *, client, model, prompt=None +) -> Union[None, str]: + if prompt is None or prompt.strip() == "": + prompt = "Write a detailed caption for this image." + + # Get the content type + content_type = stream_info.mimetype + if not content_type: + content_type, _ = mimetypes.guess_type("_dummy" + (stream_info.extension or "")) + if not content_type: + content_type = "application/octet-stream" + + # Convert to base64 + cur_pos = file_stream.tell() + try: + base64_image = base64.b64encode(file_stream.read()).decode("utf-8") + except Exception as e: + return None + finally: + file_stream.seek(cur_pos) + + # Prepare the data-uri + data_uri = f"data:{content_type};base64,{base64_image}" + + # Prepare the OpenAI API request + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": prompt}, + { + "type": "image_url", + "image_url": { + "url": data_uri, + }, + }, + ], + } + ] + + # Call the OpenAI API + response = client.chat.completions.create(model=model, messages=messages) + return response.choices[0].message.content diff --git a/packages/markitdown/src/markitdown/converters/_markdownify.py b/packages/markitdown/src/markitdown/converters/_markdownify.py index e15f607..ae99c0b 100644 --- a/packages/markitdown/src/markitdown/converters/_markdownify.py +++ b/packages/markitdown/src/markitdown/converters/_markdownify.py @@ -1,7 +1,7 @@ import re import markdownify -from typing import Any +from typing import Any, Optional from urllib.parse import quote, unquote, urlparse, urlunparse @@ -20,7 +20,14 @@ class _CustomMarkdownify(markdownify.MarkdownConverter): # Explicitly cast options to the expected type if necessary super().__init__(**options) - def convert_hn(self, n: int, el: Any, text: str, convert_as_inline: bool) -> str: + def convert_hn( + self, + n: int, + el: Any, + text: str, + convert_as_inline: Optional[bool] = False, + **kwargs, + ) -> str: """Same as usual, but be sure to start with a new line""" if not convert_as_inline: if not re.search(r"^\n", text): @@ -28,7 +35,13 @@ class _CustomMarkdownify(markdownify.MarkdownConverter): return super().convert_hn(n, el, text, convert_as_inline) # type: ignore - def convert_a(self, el: Any, text: str, convert_as_inline: bool): + def convert_a( + self, + el: Any, + text: str, + convert_as_inline: Optional[bool] = False, + **kwargs, + ): """Same as usual converter, but removes Javascript links and escapes URIs.""" prefix, suffix, text = markdownify.chomp(text) # type: ignore if not text: @@ -68,7 +81,13 @@ class _CustomMarkdownify(markdownify.MarkdownConverter): else text ) - def convert_img(self, el: Any, text: str, convert_as_inline: bool) -> str: + def convert_img( + self, + el: Any, + text: str, + convert_as_inline: Optional[bool] = False, + **kwargs, + ) -> str: """Same as usual converter, but removes data URIs""" alt = el.attrs.get("alt", None) or "" diff --git a/packages/markitdown/src/markitdown/converters/_media_converter.py b/packages/markitdown/src/markitdown/converters/_media_converter.py deleted file mode 100644 index 5c7d82b..0000000 --- a/packages/markitdown/src/markitdown/converters/_media_converter.py +++ /dev/null @@ -1,41 +0,0 @@ -import subprocess -import shutil -import json -from warnings import warn - -from ._base import DocumentConverter - - -class MediaConverter(DocumentConverter): - """ - Abstract class for multi-modal media (e.g., images and audio) - """ - - def __init__( - self, priority: float = DocumentConverter.PRIORITY_GENERIC_FILE_FORMAT - ): - super().__init__(priority=priority) - - def _get_metadata(self, local_path, exiftool_path=None): - if not exiftool_path: - which_exiftool = shutil.which("exiftool") - if which_exiftool: - warn( - f"""Implicit discovery of 'exiftool' is disabled. If you would like to continue to use exiftool in MarkItDown, please set the exiftool_path parameter in the MarkItDown consructor. E.g., - - md = MarkItDown(exiftool_path="{which_exiftool}") - -This warning will be removed in future releases. -""", - DeprecationWarning, - ) - - return None - else: - if True: - result = subprocess.run( - [exiftool_path, "-json", local_path], capture_output=True, text=True - ).stdout - return json.loads(result)[0] - # except Exception: - # return None diff --git a/packages/markitdown/src/markitdown/converters/_mp3_converter.py b/packages/markitdown/src/markitdown/converters/_mp3_converter.py deleted file mode 100644 index 91fd270..0000000 --- a/packages/markitdown/src/markitdown/converters/_mp3_converter.py +++ /dev/null @@ -1,89 +0,0 @@ -import tempfile -from typing import Union -from ._base import DocumentConverter, DocumentConverterResult -from ._wav_converter import WavConverter -from warnings import resetwarnings, catch_warnings - -# Optional Transcription support -IS_AUDIO_TRANSCRIPTION_CAPABLE = False -try: - # Using warnings' catch_warnings to catch - # pydub's warning of ffmpeg or avconv missing - with catch_warnings(record=True) as w: - import pydub - - if w: - raise ModuleNotFoundError - import speech_recognition as sr - - IS_AUDIO_TRANSCRIPTION_CAPABLE = True -except ModuleNotFoundError: - pass -finally: - resetwarnings() - - -class Mp3Converter(WavConverter): - """ - Converts MP3 files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` AND `pydub` are installed). - """ - - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a MP3 - extension = kwargs.get("file_extension", "") - if extension.lower() != ".mp3": - return None - - md_content = "" - - # Add metadata - metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) - if metadata: - for f in [ - "Title", - "Artist", - "Author", - "Band", - "Album", - "Genre", - "Track", - "DateTimeOriginal", - "CreateDate", - "Duration", - ]: - if f in metadata: - md_content += f"{f}: {metadata[f]}\n" - - # Transcribe - if IS_AUDIO_TRANSCRIPTION_CAPABLE: - handle, temp_path = tempfile.mkstemp(suffix=".wav") - os.close(handle) - try: - sound = pydub.AudioSegment.from_mp3(local_path) - sound.export(temp_path, format="wav") - - _args = dict() - _args.update(kwargs) - _args["file_extension"] = ".wav" - - try: - transcript = super()._transcribe_audio(temp_path).strip() - md_content += "\n\n### Audio Transcript:\n" + ( - "[No speech detected]" if transcript == "" else transcript - ) - except Exception: - md_content += "\n\n### Audio Transcript:\nError. Could not transcribe this audio." - - finally: - os.unlink(temp_path) - - # Return the result - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) diff --git a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py index eb7a065..8a61b0c 100644 --- a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py +++ b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py @@ -1,6 +1,7 @@ import sys -from typing import Any, Union -from ._base import DocumentConverter, DocumentConverterResult +from typing import Any, Union, BinaryIO +from .._stream_info import StreamInfo +from .._base_converter import DocumentConverter, DocumentConverterResult from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE # Try loading optional (but in this case, required) dependencies @@ -12,6 +13,12 @@ except ImportError: # Preserve the error and stack trace for later _dependency_exc_info = sys.exc_info() +ACCEPTED_MIME_TYPE_PREFIXES = [ + "application/vnd.ms-outlook", +] + +ACCEPTED_FILE_EXTENSIONS = [".msg"] + class OutlookMsgConverter(DocumentConverter): """Converts Outlook .msg files to markdown by extracting email metadata and content. @@ -21,19 +28,52 @@ class OutlookMsgConverter(DocumentConverter): - Email body content """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + # Check the extension and mimetype + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + # Brute force, check if we have an OLE file + cur_pos = file_stream.tell() + try: + if not olefile.isOleFile(file_stream): + return False + finally: + file_stream.seek(cur_pos) + + # Brue force, check if it's an Outlook file + try: + msg = olefile.OleFileIO(file_stream) + toc = "\n".join([str(stream) for stream in msg.listdir()]) + return ( + "__properties_version1.0" in toc + and "__recip_version1.0_#00000000" in toc + ) + except Exception as e: + pass + finally: + file_stream.seek(cur_pos) + + return False def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not a MSG file - extension = kwargs.get("file_extension", "") - if extension.lower() != ".msg": - return None - + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Check: the dependencies if _dependency_exc_info is not None: raise MissingDependencyException( @@ -42,44 +82,41 @@ class OutlookMsgConverter(DocumentConverter): extension=".msg", feature="outlook", ) - ) from _dependency_exc_info[1].with_traceback( + ) from _dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _dependency_exc_info[2] - ) # Restore the original traceback - - try: - msg = olefile.OleFileIO(local_path) - # Extract email metadata - md_content = "# Email Message\n\n" - - # Get headers - headers = { - "From": self._get_stream_data(msg, "__substg1.0_0C1F001F"), - "To": self._get_stream_data(msg, "__substg1.0_0E04001F"), - "Subject": self._get_stream_data(msg, "__substg1.0_0037001F"), - } - - # Add headers to markdown - for key, value in headers.items(): - if value: - md_content += f"**{key}:** {value}\n" - - md_content += "\n## Content\n\n" - - # Get email body - body = self._get_stream_data(msg, "__substg1.0_1000001F") - if body: - md_content += body - - msg.close() - - return DocumentConverterResult( - title=headers.get("Subject"), text_content=md_content.strip() ) - except Exception as e: - raise FileConversionException( - f"Could not convert MSG file '{local_path}': {str(e)}" - ) + msg = olefile.OleFileIO(file_stream) + # Extract email metadata + md_content = "# Email Message\n\n" + + # Get headers + headers = { + "From": self._get_stream_data(msg, "__substg1.0_0C1F001F"), + "To": self._get_stream_data(msg, "__substg1.0_0E04001F"), + "Subject": self._get_stream_data(msg, "__substg1.0_0037001F"), + } + + # Add headers to markdown + for key, value in headers.items(): + if value: + md_content += f"**{key}:** {value}\n" + + md_content += "\n## Content\n\n" + + # Get email body + body = self._get_stream_data(msg, "__substg1.0_1000001F") + if body: + md_content += body + + msg.close() + + return DocumentConverterResult( + markdown=md_content.strip(), + title=headers.get("Subject"), + ) def _get_stream_data(self, msg: Any, stream_path: str) -> Union[str, None]: """Helper to safely extract and decode stream data from the MSG file.""" diff --git a/packages/markitdown/src/markitdown/converters/_pdf_converter.py b/packages/markitdown/src/markitdown/converters/_pdf_converter.py index 3c5ecad..4586ef1 100644 --- a/packages/markitdown/src/markitdown/converters/_pdf_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pdf_converter.py @@ -1,8 +1,15 @@ import sys -from typing import Union -from ._base import DocumentConverter, DocumentConverterResult +import io + +from typing import BinaryIO, Any + + +from ._html_converter import HtmlConverter +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE + # Try loading optional (but in this case, required) dependencies # Save reporting of any exceptions for later _dependency_exc_info = None @@ -14,22 +21,43 @@ except ImportError: _dependency_exc_info = sys.exc_info() +ACCEPTED_MIME_TYPE_PREFIXES = [ + "application/pdf", + "application/x-pdf", +] + +ACCEPTED_FILE_EXTENSIONS = [".pdf"] + + class PdfConverter(DocumentConverter): """ Converts PDFs to Markdown. Most style information is ignored, so the results are essentially plain-text. """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a PDF - extension = kwargs.get("file_extension", "") - if extension.lower() != ".pdf": - return None + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Check the dependencies if _dependency_exc_info is not None: raise MissingDependencyException( @@ -38,11 +66,13 @@ class PdfConverter(DocumentConverter): extension=".pdf", feature="pdf", ) - ) from _dependency_exc_info[1].with_traceback( + ) from _dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _dependency_exc_info[2] - ) # Restore the original traceback + ) + assert isinstance(file_stream, io.IOBase) # for mypy return DocumentConverterResult( - title=None, - text_content=pdfminer.high_level.extract_text(local_path), + markdown=pdfminer.high_level.extract_text(file_stream), ) diff --git a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py index b4c9282..4a21d3a 100644 --- a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py +++ b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py @@ -1,13 +1,26 @@ -import mimetypes +import sys -from charset_normalizer import from_path -from typing import Any, Union +from typing import BinaryIO, Any +from charset_normalizer import from_bytes +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo -from ._base import DocumentConverter, DocumentConverterResult +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + import mammoth +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() +ACCEPTED_MIME_TYPE_PREFIXES = [ + "text/", + "application/json", +] # Mimetypes to ignore (commonly confused extensions) -IGNORE_MIMETYPES = [ +IGNORE_MIME_TYPE_PREFIXES = [ "text/vnd.in3d.spot", # .spo wich is confused with xls, doc, etc. "text/vnd.graphviz", # .dot which is confused with xls, doc, etc. ] @@ -16,34 +29,34 @@ IGNORE_MIMETYPES = [ class PlainTextConverter(DocumentConverter): """Anything with content type text/plain""" - def __init__( - self, priority: float = DocumentConverter.PRIORITY_GENERIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + for prefix in IGNORE_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return False + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Guess the content type from any file extension that might be around - content_type, _ = mimetypes.guess_type( - "__placeholder" + kwargs.get("file_extension", "") - ) + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + if stream_info.charset: + text_content = file_stream.read().decode(stream_info.charset) + else: + text_content = str(from_bytes(file_stream.read()).best()) - # Ignore common false positives - if content_type in IGNORE_MIMETYPES: - content_type = None - - # Only accept text files - if content_type is None: - return None - elif all( - not content_type.lower().startswith(type_prefix) - for type_prefix in ["text/", "application/json"] - ): - return None - - text_content = str(from_path(local_path).best()) - return DocumentConverterResult( - title=None, - text_content=text_content, - ) + return DocumentConverterResult(markdown=text_content) diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index 431b6a0..bea1226 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -1,12 +1,16 @@ +import sys import base64 +import os +import io import re import html -import sys -from typing import Union +from typing import BinaryIO, Any -from ._base import DocumentConverterResult, DocumentConverter from ._html_converter import HtmlConverter +from ._llm_caption import llm_caption +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE # Try loading optional (but in this case, required) dependencies @@ -19,51 +23,46 @@ except ImportError: _dependency_exc_info = sys.exc_info() -class PptxConverter(HtmlConverter): +ACCEPTED_MIME_TYPE_PREFIXES = [ + "application/vnd.openxmlformats-officedocument.presentationml", +] + +ACCEPTED_FILE_EXTENSIONS = [".pptx"] + + +class PptxConverter(DocumentConverter): """ Converts PPTX files to Markdown. Supports heading, tables and images with alt text. """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def __init__(self): + super().__init__() + self._html_converter = HtmlConverter() - def _get_llm_description( - self, llm_client, llm_model, image_blob, content_type, prompt=None - ): - if prompt is None or prompt.strip() == "": - prompt = "Write a detailed alt text for this image with less than 50 words." + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() - image_base64 = base64.b64encode(image_blob).decode("utf-8") - data_uri = f"data:{content_type};base64,{image_base64}" + if extension in ACCEPTED_FILE_EXTENSIONS: + return True - messages = [ - { - "role": "user", - "content": [ - { - "type": "image_url", - "image_url": { - "url": data_uri, - }, - }, - {"type": "text", "text": prompt}, - ], - } - ] + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True - response = llm_client.chat.completions.create( - model=llm_model, messages=messages - ) - return response.choices[0].message.content - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a PPTX - extension = kwargs.get("file_extension", "") - if extension.lower() != ".pptx": - return None + return False + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Check the dependencies if _dependency_exc_info is not None: raise MissingDependencyException( @@ -72,11 +71,14 @@ class PptxConverter(HtmlConverter): extension=".pptx", feature="pptx", ) - ) from _dependency_exc_info[1].with_traceback( + ) from _dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _dependency_exc_info[2] - ) # Restore the original traceback + ) - presentation = pptx.Presentation(local_path) + # Perform the conversion + presentation = pptx.Presentation(file_stream) md_content = "" slide_num = 0 for slide in presentation.slides: @@ -92,59 +94,58 @@ class PptxConverter(HtmlConverter): if self._is_picture(shape): # https://github.com/scanny/python-pptx/pull/512#issuecomment-1713100069 - llm_description = None - alt_text = None + llm_description = "" + alt_text = "" + # Potentially generate a description using an LLM llm_client = kwargs.get("llm_client") llm_model = kwargs.get("llm_model") if llm_client is not None and llm_model is not None: + # Prepare a file_stream and stream_info for the image data + image_filename = shape.image.filename + image_extension = None + if image_filename: + image_extension = os.path.splitext(image_filename)[1] + image_stream_info = StreamInfo( + mimetype=shape.image.content_type, + extension=image_extension, + filename=image_filename, + ) + + image_stream = io.BytesIO(shape.image.blob) + + # Caption the image try: - llm_description = self._get_llm_description( - llm_client, - llm_model, - shape.image.blob, - shape.image.content_type, + llm_description = llm_caption( + image_stream, + image_stream_info, + client=llm_client, + model=llm_model, + prompt=kwargs.get("llm_prompt"), ) except Exception: - # Unable to describe with LLM + # Unable to generate a description pass - if not llm_description: - try: - alt_text = shape._element._nvXxPr.cNvPr.attrib.get( - "descr", "" - ) - except Exception: - # Unable to get alt text - pass + # Also grab any description embedded in the deck + try: + alt_text = shape._element._nvXxPr.cNvPr.attrib.get("descr", "") + except Exception: + # Unable to get alt text + pass + + # Prepare the alt, escaping any special characters + alt_text = "\n".join([llm_description, alt_text]) or shape.name + alt_text = re.sub(r"[\r\n\[\]]", " ", alt_text) + alt_text = re.sub(r"\s+", " ", alt_text).strip() # A placeholder name filename = re.sub(r"\W", "", shape.name) + ".jpg" - md_content += ( - "\n![" - + (llm_description or alt_text or shape.name) - + "](" - + filename - + ")\n" - ) + md_content += "\n![" + alt_text + "](" + filename + ")\n" # Tables if self._is_table(shape): - html_table = "" - first_row = True - for row in shape.table.rows: - html_table += "" - for cell in row.cells: - if first_row: - html_table += "" - else: - html_table += "" - html_table += "" - first_row = False - html_table += "
" + html.escape(cell.text) + "" + html.escape(cell.text) + "
" - md_content += ( - "\n" + self._convert(html_table).text_content.strip() + "\n" - ) + md_content += self._convert_table_to_markdown(shape.table) # Charts if shape.has_chart: @@ -174,10 +175,7 @@ class PptxConverter(HtmlConverter): md_content += notes_frame.text md_content = md_content.strip() - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) + return DocumentConverterResult(markdown=md_content.strip()) def _is_picture(self, shape): if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.PICTURE: @@ -192,6 +190,23 @@ class PptxConverter(HtmlConverter): return True return False + def _convert_table_to_markdown(self, table): + # Write the table as HTML, then convert it to Markdown + html_table = "" + first_row = True + for row in table.rows: + html_table += "" + for cell in row.cells: + if first_row: + html_table += "" + else: + html_table += "" + html_table += "" + first_row = False + html_table += "
" + html.escape(cell.text) + "" + html.escape(cell.text) + "
" + + return self._html_converter.convert_string(html_table).markdown.strip() + "\n" + def _convert_chart_to_markdown(self, chart): md = "\n\n### Chart" if chart.has_title: diff --git a/packages/markitdown/src/markitdown/converters/_rss_converter.py b/packages/markitdown/src/markitdown/converters/_rss_converter.py index b279c85..dbafc1b 100644 --- a/packages/markitdown/src/markitdown/converters/_rss_converter.py +++ b/packages/markitdown/src/markitdown/converters/_rss_converter.py @@ -1,128 +1,165 @@ from xml.dom import minidom -from typing import Union +from typing import BinaryIO, Any, Union from bs4 import BeautifulSoup from ._markdownify import _CustomMarkdownify -from ._base import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo +from .._base_converter import DocumentConverter, DocumentConverterResult + +PRECISE_MIME_TYPE_PREFIXES = [ + "application/rss", + "application/atom", +] + +PRECISE_FILE_EXTENSIONS = [".rss", ".atom"] + +CANDIDATE_MIME_TYPE_PREFIXES = [ + "text/xml", + "application/xml", +] + +CANDIDATE_FILE_EXTENSIONS = [ + ".xml", +] class RssConverter(DocumentConverter): """Convert RSS / Atom type to markdown""" - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() - def convert( - self, local_path: str, **kwargs - ) -> Union[None, DocumentConverterResult]: - # Bail if not RSS type - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".xml", ".rss", ".atom"]: - return None + # Check for precise mimetypes and file extensions + if extension in PRECISE_FILE_EXTENSIONS: + return True + + for prefix in PRECISE_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + # Check for precise mimetypes and file extensions + if extension in CANDIDATE_FILE_EXTENSIONS: + return self._check_xml(file_stream) + + for prefix in CANDIDATE_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return self._check_xml(file_stream) + + return False + + def _check_xml(self, file_stream: BinaryIO) -> bool: + cur_pos = file_stream.tell() try: - doc = minidom.parse(local_path) + doc = minidom.parse(file_stream) + return self._feed_type(doc) is not None except BaseException as _: - return None - result = None + pass + finally: + file_stream.seek(cur_pos) + return False + + def _feed_type(self, doc: Any) -> str: if doc.getElementsByTagName("rss"): - # A RSS feed must have a root element of - result = self._parse_rss_type(doc) + return "rss" elif doc.getElementsByTagName("feed"): root = doc.getElementsByTagName("feed")[0] if root.getElementsByTagName("entry"): # An Atom feed must have a root element of and at least one - result = self._parse_atom_type(doc) - else: - return None + return "atom" + return None + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + doc = minidom.parse(file_stream) + feed_type = self._feed_type(doc) + + if feed_type == "rss": + return self._parse_rss_type(doc) + elif feed_type == "atom": + return self._parse_atom_type(doc) else: - # not rss or atom - return None + raise ValueError("Unknown feed type") - return result - - def _parse_atom_type( - self, doc: minidom.Document - ) -> Union[None, DocumentConverterResult]: + def _parse_atom_type(self, doc: minidom.Document) -> DocumentConverterResult: """Parse the type of an Atom feed. Returns None if the feed type is not recognized or something goes wrong. """ - try: - root = doc.getElementsByTagName("feed")[0] - title = self._get_data_by_tag_name(root, "title") - subtitle = self._get_data_by_tag_name(root, "subtitle") - entries = root.getElementsByTagName("entry") - md_text = f"# {title}\n" - if subtitle: - md_text += f"{subtitle}\n" - for entry in entries: - entry_title = self._get_data_by_tag_name(entry, "title") - entry_summary = self._get_data_by_tag_name(entry, "summary") - entry_updated = self._get_data_by_tag_name(entry, "updated") - entry_content = self._get_data_by_tag_name(entry, "content") + root = doc.getElementsByTagName("feed")[0] + title = self._get_data_by_tag_name(root, "title") + subtitle = self._get_data_by_tag_name(root, "subtitle") + entries = root.getElementsByTagName("entry") + md_text = f"# {title}\n" + if subtitle: + md_text += f"{subtitle}\n" + for entry in entries: + entry_title = self._get_data_by_tag_name(entry, "title") + entry_summary = self._get_data_by_tag_name(entry, "summary") + entry_updated = self._get_data_by_tag_name(entry, "updated") + entry_content = self._get_data_by_tag_name(entry, "content") - if entry_title: - md_text += f"\n## {entry_title}\n" - if entry_updated: - md_text += f"Updated on: {entry_updated}\n" - if entry_summary: - md_text += self._parse_content(entry_summary) - if entry_content: - md_text += self._parse_content(entry_content) + if entry_title: + md_text += f"\n## {entry_title}\n" + if entry_updated: + md_text += f"Updated on: {entry_updated}\n" + if entry_summary: + md_text += self._parse_content(entry_summary) + if entry_content: + md_text += self._parse_content(entry_content) - return DocumentConverterResult( - title=title, - text_content=md_text, - ) - except BaseException as _: - return None + return DocumentConverterResult( + markdown=md_text, + title=title, + ) - def _parse_rss_type( - self, doc: minidom.Document - ) -> Union[None, DocumentConverterResult]: + def _parse_rss_type(self, doc: minidom.Document) -> DocumentConverterResult: """Parse the type of an RSS feed. Returns None if the feed type is not recognized or something goes wrong. """ - try: - root = doc.getElementsByTagName("rss")[0] - channel = root.getElementsByTagName("channel") - if not channel: - return None - channel = channel[0] - channel_title = self._get_data_by_tag_name(channel, "title") - channel_description = self._get_data_by_tag_name(channel, "description") - items = channel.getElementsByTagName("item") - if channel_title: - md_text = f"# {channel_title}\n" - if channel_description: - md_text += f"{channel_description}\n" - if not items: - items = [] - for item in items: - title = self._get_data_by_tag_name(item, "title") - description = self._get_data_by_tag_name(item, "description") - pubDate = self._get_data_by_tag_name(item, "pubDate") - content = self._get_data_by_tag_name(item, "content:encoded") - - if title: - md_text += f"\n## {title}\n" - if pubDate: - md_text += f"Published on: {pubDate}\n" - if description: - md_text += self._parse_content(description) - if content: - md_text += self._parse_content(content) - - return DocumentConverterResult( - title=channel_title, - text_content=md_text, - ) - except BaseException as _: - print(traceback.format_exc()) + root = doc.getElementsByTagName("rss")[0] + channel = root.getElementsByTagName("channel") + if not channel: return None + channel = channel[0] + channel_title = self._get_data_by_tag_name(channel, "title") + channel_description = self._get_data_by_tag_name(channel, "description") + items = channel.getElementsByTagName("item") + if channel_title: + md_text = f"# {channel_title}\n" + if channel_description: + md_text += f"{channel_description}\n" + if not items: + items = [] + for item in items: + title = self._get_data_by_tag_name(item, "title") + description = self._get_data_by_tag_name(item, "description") + pubDate = self._get_data_by_tag_name(item, "pubDate") + content = self._get_data_by_tag_name(item, "content:encoded") + + if title: + md_text += f"\n## {title}\n" + if pubDate: + md_text += f"Published on: {pubDate}\n" + if description: + md_text += self._parse_content(description) + if content: + md_text += self._parse_content(content) + + return DocumentConverterResult( + markdown=md_text, + title=channel_title, + ) def _parse_content(self, content: str) -> str: """Parse the content of an RSS feed item""" diff --git a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py new file mode 100644 index 0000000..3d02173 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py @@ -0,0 +1,43 @@ +import io +import sys +from typing import BinaryIO +from .._exceptions import MissingDependencyException + +# Try loading optional (but in this case, required) dependencies +# Save reporting of any exceptions for later +_dependency_exc_info = None +try: + import speech_recognition as sr + import pydub +except ImportError: + # Preserve the error and stack trace for later + _dependency_exc_info = sys.exc_info() + + +def transcribe_audio(file_stream: BinaryIO, *, audio_format: str = "wav") -> str: + # Check for installed dependencies + if _dependency_exc_info is not None: + raise MissingDependencyException( + "Speech transcription requires installing MarkItdown with the [audio-transcription] optional dependencies. E.g., `pip install markitdown[audio-transcription]` or `pip install markitdown[all]`" + ) from _dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] + _dependency_exc_info[2] + ) + + if audio_format in ["wav", "aiff", "flac"]: + audio_source = file_stream + elif audio_format in ["mp3", "mp4"]: + audio_segment = pydub.AudioSegment.from_file(file_stream, format=audio_format) + + audio_source = io.BytesIO() + audio_segment.export(audio_source, format="wav") + audio_source.seek(0) + else: + raise ValueError(f"Unsupported audio format: {audio_format}") + + recognizer = sr.Recognizer() + with sr.AudioFile(audio_source) as source: + audio = recognizer.record(source) + transcript = recognizer.recognize_google(audio).strip() + return "[No speech detected]" if transcript == "" else transcript diff --git a/packages/markitdown/src/markitdown/converters/_wav_converter.py b/packages/markitdown/src/markitdown/converters/_wav_converter.py deleted file mode 100644 index 3c8d842..0000000 --- a/packages/markitdown/src/markitdown/converters/_wav_converter.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Union -from ._base import DocumentConverter, DocumentConverterResult -from ._media_converter import MediaConverter - -# Optional Transcription support -IS_AUDIO_TRANSCRIPTION_CAPABLE = False -try: - import speech_recognition as sr - - IS_AUDIO_TRANSCRIPTION_CAPABLE = True -except ModuleNotFoundError: - pass - - -class WavConverter(MediaConverter): - """ - Converts WAV files to markdown via extraction of metadata (if `exiftool` is installed), and speech transcription (if `speech_recognition` is installed). - """ - - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) - - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a WAV - extension = kwargs.get("file_extension", "") - if extension.lower() != ".wav": - return None - - md_content = "" - - # Add metadata - metadata = self._get_metadata(local_path, kwargs.get("exiftool_path")) - if metadata: - for f in [ - "Title", - "Artist", - "Author", - "Band", - "Album", - "Genre", - "Track", - "DateTimeOriginal", - "CreateDate", - "Duration", - ]: - if f in metadata: - md_content += f"{f}: {metadata[f]}\n" - - # Transcribe - if IS_AUDIO_TRANSCRIPTION_CAPABLE: - try: - transcript = self._transcribe_audio(local_path) - md_content += "\n\n### Audio Transcript:\n" + ( - "[No speech detected]" if transcript == "" else transcript - ) - except Exception: - md_content += ( - "\n\n### Audio Transcript:\nError. Could not transcribe this audio." - ) - - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) - - def _transcribe_audio(self, local_path) -> str: - recognizer = sr.Recognizer() - with sr.AudioFile(local_path) as source: - audio = recognizer.record(source) - return recognizer.recognize_google(audio).strip() diff --git a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py index f27fe23..5b054af 100644 --- a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py +++ b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py @@ -1,35 +1,63 @@ +import io import re - -from typing import Any, Union +from typing import Any, BinaryIO, Optional from bs4 import BeautifulSoup -from ._base import DocumentConverter, DocumentConverterResult +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo from ._markdownify import _CustomMarkdownify +ACCEPTED_MIME_TYPE_PREFIXES = [ + "text/html", + "application/xhtml", +] + +ACCEPTED_FILE_EXTENSIONS = [ + ".html", + ".htm", +] + class WikipediaConverter(DocumentConverter): """Handle Wikipedia pages separately, focusing only on the main document content.""" - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + """ + Make sure we're dealing with HTML content *from* Wikipedia. + """ + + url = stream_info.url or "" + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if not re.search(r"^https?:\/\/[a-zA-Z]{2,3}\.wikipedia.org\/", url): + # Not a Wikipedia URL + return False + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + # Not HTML content + return False def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not Wikipedia - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - url = kwargs.get("url", "") - if not re.search(r"^https?:\/\/[a-zA-Z]{2,3}\.wikipedia.org\/", url): - return None - - # Parse the file - soup = None - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + # Parse the stream + encoding = "utf-8" if stream_info.charset is None else stream_info.charset + soup = BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) # Remove javascript and style blocks for script in soup(["script", "style"]): @@ -56,6 +84,6 @@ class WikipediaConverter(DocumentConverter): webpage_text = _CustomMarkdownify().convert_soup(soup) return DocumentConverterResult( + markdown=webpage_text, title=main_title, - text_content=webpage_text, ) diff --git a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py index 56398ca..3d0e1ab 100644 --- a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py @@ -1,10 +1,9 @@ import sys - -from typing import Union - -from ._base import DocumentConverter, DocumentConverterResult +from typing import BinaryIO, Any from ._html_converter import HtmlConverter +from .._base_converter import DocumentConverter, DocumentConverterResult from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE +from .._stream_info import StreamInfo # Try loading optional (but in this case, required) dependencies # Save reporting of any exceptions for later @@ -22,23 +21,51 @@ try: except ImportError: _xls_dependency_exc_info = sys.exc_info() +ACCEPTED_XLSX_MIME_TYPE_PREFIXES = [ + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" +] +ACCEPTED_XLSX_FILE_EXTENSIONS = [".xlsx"] -class XlsxConverter(HtmlConverter): +ACCEPTED_XLS_MIME_TYPE_PREFIXES = [ + "application/vnd.ms-excel", + "application/excel", +] +ACCEPTED_XLS_FILE_EXTENSIONS = [".xls"] + + +class XlsxConverter(DocumentConverter): """ Converts XLSX files to Markdown, with each sheet presented as a separate Markdown table. """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def __init__(self): + super().__init__() + self._html_converter = HtmlConverter() - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a XLSX - extension = kwargs.get("file_extension", "") - if extension.lower() != ".xlsx": - return None + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + if extension in ACCEPTED_XLSX_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_XLSX_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Check the dependencies if _xlsx_dependency_exc_info is not None: raise MissingDependencyException( @@ -47,34 +74,58 @@ class XlsxConverter(HtmlConverter): extension=".xlsx", feature="xlsx", ) - ) from _xlsx_dependency_exc_info[1].with_traceback( + ) from _xlsx_dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _xlsx_dependency_exc_info[2] - ) # Restore the original traceback + ) - sheets = pd.read_excel(local_path, sheet_name=None, engine="openpyxl") + sheets = pd.read_excel(file_stream, sheet_name=None, engine="openpyxl") md_content = "" for s in sheets: md_content += f"## {s}\n" html_content = sheets[s].to_html(index=False) - md_content += self._convert(html_content).text_content.strip() + "\n\n" + md_content += ( + self._html_converter.convert_string(html_content).markdown.strip() + + "\n\n" + ) - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) + return DocumentConverterResult(markdown=md_content.strip()) -class XlsConverter(HtmlConverter): +class XlsConverter(DocumentConverter): """ Converts XLS files to Markdown, with each sheet presented as a separate Markdown table. """ - def convert(self, local_path, **kwargs) -> Union[None, DocumentConverterResult]: - # Bail if not a XLS - extension = kwargs.get("file_extension", "") - if extension.lower() != ".xls": - return None + def __init__(self): + super().__init__() + self._html_converter = HtmlConverter() + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_XLS_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_XLS_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: # Load the dependencies if _xls_dependency_exc_info is not None: raise MissingDependencyException( @@ -83,18 +134,20 @@ class XlsConverter(HtmlConverter): extension=".xls", feature="xls", ) - ) from _xls_dependency_exc_info[1].with_traceback( + ) from _xls_dependency_exc_info[ + 1 + ].with_traceback( # type: ignore[union-attr] _xls_dependency_exc_info[2] - ) # Restore the original traceback + ) - sheets = pd.read_excel(local_path, sheet_name=None, engine="xlrd") + sheets = pd.read_excel(file_stream, sheet_name=None, engine="xlrd") md_content = "" for s in sheets: md_content += f"## {s}\n" html_content = sheets[s].to_html(index=False) - md_content += self._convert(html_content).text_content.strip() + "\n\n" + md_content += ( + self._html_converter.convert_string(html_content).markdown.strip() + + "\n\n" + ) - return DocumentConverterResult( - title=None, - text_content=md_content.strip(), - ) + return DocumentConverterResult(markdown=md_content.strip()) diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py index e61b208..5a158d5 100644 --- a/packages/markitdown/src/markitdown/converters/_youtube_converter.py +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -1,14 +1,15 @@ -import re +import sys import json -import urllib.parse import time - -from typing import Any, Union, Dict, List -from urllib.parse import parse_qs, urlparse +import io +import re +from typing import Any, BinaryIO, Optional, Dict, List, Union +from urllib.parse import parse_qs, urlparse, unquote from bs4 import BeautifulSoup -from ._base import DocumentConverter, DocumentConverterResult - +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo +from ._markdownify import _CustomMarkdownify # Optional YouTube transcription support try: @@ -19,53 +20,59 @@ except ModuleNotFoundError: IS_YOUTUBE_TRANSCRIPT_CAPABLE = False +ACCEPTED_MIME_TYPE_PREFIXES = [ + "text/html", + "application/xhtml", +] + +ACCEPTED_FILE_EXTENSIONS = [ + ".html", + ".htm", +] + + class YouTubeConverter(DocumentConverter): """Handle YouTube specially, focusing on the video title, description, and transcript.""" - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + """ + Make sure we're dealing with HTML content *from* YouTube. + """ + url = stream_info.url or "" + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() - def retry_operation(self, operation, retries=3, delay=2): - """Retries the operation if it fails.""" - attempt = 0 - while attempt < retries: - try: - return operation() # Attempt the operation - except Exception as e: - print(f"Attempt {attempt + 1} failed: {e}") - if attempt < retries - 1: - time.sleep(delay) # Wait before retrying - attempt += 1 - # If all attempts fail, raise the last exception - raise Exception(f"Operation failed after {retries} attempts.") - - def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not YouTube - extension = kwargs.get("file_extension", "") - if extension.lower() not in [".html", ".htm"]: - return None - url = kwargs.get("url", "") - - url = urllib.parse.unquote(url) + url = unquote(url) url = url.replace(r"\?", "?").replace(r"\=", "=") if not url.startswith("https://www.youtube.com/watch?"): - return None + # Not a YouTube URL + return False - # Parse the file with error handling - try: - with open(local_path, "rt", encoding="utf-8") as fh: - soup = BeautifulSoup(fh.read(), "html.parser") - except Exception as e: - print(f"Error reading YouTube page: {e}") - return None + if extension in ACCEPTED_FILE_EXTENSIONS: + return True - if not soup.title or not soup.title.string: - return None + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + # Not HTML content + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + # Parse the stream + encoding = "utf-8" if stream_info.charset is None else stream_info.charset + soup = BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) # Read the meta tags metadata: Dict[str, str] = {"title": soup.title.string} @@ -126,7 +133,7 @@ class YouTubeConverter(DocumentConverter): if IS_YOUTUBE_TRANSCRIPT_CAPABLE: transcript_text = "" - parsed_url = urlparse(url) # type: ignore + parsed_url = urlparse(stream_info.url) # type: ignore params = parse_qs(parsed_url.query) # type: ignore if "v" in params and params["v"][0]: video_id = str(params["v"][0]) @@ -135,7 +142,7 @@ class YouTubeConverter(DocumentConverter): "youtube_transcript_languages", ("en",) ) # Retry the transcript fetching operation - transcript = self.retry_operation( + transcript = self._retry_operation( lambda: YouTubeTranscriptApi.get_transcript( video_id, languages=youtube_transcript_languages ), @@ -158,8 +165,8 @@ class YouTubeConverter(DocumentConverter): assert isinstance(title, str) return DocumentConverterResult( + markdown=webpage_text, title=title, - text_content=webpage_text, ) def _get( @@ -188,3 +195,17 @@ class YouTubeConverter(DocumentConverter): if result := self._findKey(v, key): return result return None + + def _retry_operation(self, operation, retries=3, delay=2): + """Retries the operation if it fails.""" + attempt = 0 + while attempt < retries: + try: + return operation() # Attempt the operation + except Exception as e: + print(f"Attempt {attempt + 1} failed: {e}") + if attempt < retries - 1: + time.sleep(delay) # Wait before retrying + attempt += 1 + # If all attempts fail, raise the last exception + raise Exception(f"Operation failed after {retries} attempts.") diff --git a/packages/markitdown/src/markitdown/converters/_zip_converter.py b/packages/markitdown/src/markitdown/converters/_zip_converter.py index e2b5fe6..cb1a7e6 100644 --- a/packages/markitdown/src/markitdown/converters/_zip_converter.py +++ b/packages/markitdown/src/markitdown/converters/_zip_converter.py @@ -1,9 +1,23 @@ -import os +import sys import zipfile -import shutil -from typing import Any, Union +import io +import os -from ._base import DocumentConverter, DocumentConverterResult +from typing import BinaryIO, Any, TYPE_CHECKING + +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo +from .._exceptions import UnsupportedFormatException, FileConversionException + +# Break otherwise circular import for type hinting +if TYPE_CHECKING: + from .._markitdown import MarkItDown + +ACCEPTED_MIME_TYPE_PREFIXES = [ + "application/zip", +] + +ACCEPTED_FILE_EXTENSIONS = [".zip"] class ZipConverter(DocumentConverter): @@ -46,99 +60,58 @@ class ZipConverter(DocumentConverter): """ def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT + self, + *, + markitdown: "MarkItDown", ): - super().__init__(priority=priority) + super().__init__() + self._markitdown = markitdown + + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False def convert( - self, local_path: str, **kwargs: Any - ) -> Union[None, DocumentConverterResult]: - # Bail if not a ZIP - extension = kwargs.get("file_extension", "") - if extension.lower() != ".zip": - return None + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + file_path = stream_info.url or stream_info.local_path or stream_info.filename + md_content = f"Content from the zip file `{file_path}`:\n\n" - # Get parent converters list if available - parent_converters = kwargs.get("_parent_converters", []) - if not parent_converters: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] No converters available to process zip contents from: {local_path}", - ) + with zipfile.ZipFile(file_stream, "r") as zipObj: + for name in zipObj.namelist(): + try: + z_file_stream = io.BytesIO(zipObj.read(name)) + z_file_stream_info = StreamInfo( + extension=os.path.splitext(name)[1], + filename=os.path.basename(name), + ) + result = self._markitdown.convert_stream( + stream=z_file_stream, + stream_info=z_file_stream_info, + ) + if result is not None: + md_content += f"## File: {name}\n\n" + md_content += result.markdown + "\n\n" + except UnsupportedFormatException: + pass + except FileConversionException: + pass - extracted_zip_folder_name = ( - f"extracted_{os.path.basename(local_path).replace('.zip', '_zip')}" - ) - extraction_dir = os.path.normpath( - os.path.join(os.path.dirname(local_path), extracted_zip_folder_name) - ) - md_content = f"Content from the zip file `{os.path.basename(local_path)}`:\n\n" - - try: - # Extract the zip file safely - with zipfile.ZipFile(local_path, "r") as zipObj: - # Bail if we discover it's an Office OOXML file - if "[Content_Types].xml" in zipObj.namelist(): - return None - - # Safeguard against path traversal - for member in zipObj.namelist(): - member_path = os.path.normpath(os.path.join(extraction_dir, member)) - if ( - not os.path.commonprefix([extraction_dir, member_path]) - == extraction_dir - ): - raise ValueError( - f"Path traversal detected in zip file: {member}" - ) - - # Extract all files safely - zipObj.extractall(path=extraction_dir) - - # Process each extracted file - for root, dirs, files in os.walk(extraction_dir): - for name in files: - file_path = os.path.join(root, name) - relative_path = os.path.relpath(file_path, extraction_dir) - - # Get file extension - _, file_extension = os.path.splitext(name) - - # Update kwargs for the file - file_kwargs = kwargs.copy() - file_kwargs["file_extension"] = file_extension - file_kwargs["_parent_converters"] = parent_converters - - # Try converting the file using available converters - for converter in parent_converters: - # Skip the zip converter to avoid infinite recursion - if isinstance(converter, ZipConverter): - continue - - result = converter.convert(file_path, **file_kwargs) - if result is not None: - md_content += f"\n## File: {relative_path}\n\n" - md_content += result.text_content + "\n\n" - break - - # Clean up extracted files if specified - if kwargs.get("cleanup_extracted", True): - shutil.rmtree(extraction_dir) - - return DocumentConverterResult(title=None, text_content=md_content.strip()) - - except zipfile.BadZipFile: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] Invalid or corrupted zip file: {local_path}", - ) - except ValueError as ve: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] Security error in zip file {local_path}: {str(ve)}", - ) - except Exception as e: - return DocumentConverterResult( - title=None, - text_content=f"[ERROR] Failed to process zip file {local_path}: {str(e)}", - ) + return DocumentConverterResult(markdown=md_content.strip()) diff --git a/packages/markitdown/tests/test_cli.py b/packages/markitdown/tests/test_cli.py index 1e2b095..7c8afc2 100644 --- a/packages/markitdown/tests/test_cli.py +++ b/packages/markitdown/tests/test_cli.py @@ -7,7 +7,7 @@ from markitdown import __version__ try: from .test_markitdown import TEST_FILES_DIR, DOCX_TEST_STRINGS except ImportError: - from test_markitdown import TEST_FILES_DIR, DOCX_TEST_STRINGS + from test_markitdown import TEST_FILES_DIR, DOCX_TEST_STRINGS # type: ignore @pytest.fixture(scope="session") diff --git a/packages/markitdown/tests/test_files/test.m4a b/packages/markitdown/tests/test_files/test.m4a new file mode 100755 index 0000000000000000000000000000000000000000..7a3b25f85c71f24d4f065752dd3971377377f7fe GIT binary patch literal 165924 zcmeFZ1za3Wx-JeuLXZ&LNpL3#?(Q(?3_iHS;4Vq<;10pv1_pNv?(PyG!Cis{2+4(f z-#K^xM|StyvwP0o|Go9=nW}p0EqkA;uI_%i8V(MQ(9GGx9%9eI26yl7{nwUGc98oZ zy^D*biEbgvEnB1*QjwwQ(?QgP#CtjV>sPF=SlPHqo?955eF3=6An~;o$BS{zd1P?Ee$Fm;9T!$iHU%4+9AI z8ETy!4Xy7}b)Bs(OzwU7hdO_+|IvSx>sJ;3rk+1vFT4uG#M1D-tQQcIUv>QI*Pr$c z)cva_77G&_$A78Q$Tm2?cyN{d57}}cHnEulF4zaX-Fw!$8$Zv)Io{{?A#t(8* z6GziuM!XL{G#A~)(dNbP;dUoyBb)oMiIcO_L#H3ygAVW>)Q5&XxQF;}?h)LxhrsVJ zyfSq%ae9a&ynFZV`Mr;L@8gQ#!6C!JJv#dJL%Nqd!iq+Qe=y)PCuiqh`uE`ncOQ2$ z{w?_HANM})`6uy*;JuEq-Ji%F^89HT^bl85_xto0&W^wQ2i1qV{&$;yh2TXXc6PsP z1Tp(p8{MbC)8B*s%SO(>$se?T2OHt~p|E=$L}y1ATX;CkhcwJzA~?i*?@!0Xzl-1N z{<(C7AAdgm-<5~-7xdBod^*DSKc9|x-`GFbUUHO&$w~1Hen*hxXZvq9_<#Qo7T`X7 z_kW8aJpUj5{~qf9i|}`$Ai-hdKn#Y-%F6nBH!u-t% z9j*N?L+5Y!J=^8-r{87p!EV3nVe0(rQvbgzxM*(pP4QmO{`bsRZuY;)kjeg~A(zW5 z?GG}{-MbeEak3UOid}pCZiqruW%SfmyNEZ$@i#<2* z8fVIbsmD94k!NI2q-|H}_L%i_;|G28$$Jtz8E2ujPl7W&g3=3U#Ld`9!I3QIb{o0&CsJj3$N|nma7yYxqZ+C zDejymb8o_PRPRkUX@#~re$=i)9uV{_DQ_V$M22=eYLwe6n=`ROc=nuLe=lN{`J2of z9h{#IIQC7}SO)FEc=Ve3NxZX^K_GK2zFTOzV^eae#u53V!46aXBn6G=;aU{jnM6-T zLF#nIiN-h6*P}~xCRbxJ12U~rLmy?uQi8G#!Nhx#-e^58HIVaz*YS()YgP)ziK3!Q z#EGPqu@y4{iRC2ki&?ahkC9{#w8!OjMxCO>4@sup{jK2s75RAX@u%|1WiDZ2Mn1}l zXT*N@ylt7SHW9%7^_U2)d@~()V7md9nZD!krL~VGeu^^%yjNd>lVS5srdCIa{eq)G zSk^oxt?)5TJn_k1`ju`~`lSStSEq7ndNj6e18S(NpFABs$~xe4lReGTGVTERPD#ym z4wDow{c4`%vSSY>y{?pmA-Xb5{@uzWW?mJ~x@OXh=9r>Xn|uv4M~QWg96lFu((jg@ za|*tmO0U4qRWb8Z*(M-<$Y@A#k_WF^&0^oG#j+sR>#fV?P0n4;6l>26j}(vUm>bKc zQ1d;x67mEZmiTq>(75(l#&Zujma$4Y;;Z(2eh|K*2E0-f`xBGeK30yj{Q@zpOmQ*! z;)CHjqhyIHAy+E>L@~I>*_RR5|40P>3XuMRDi8Dj9)BEWw$3c>EI2rC_)NI`_sL=K z92AAP4n`d#?^5Dn!~3PXVtlHfaKFqZhm5eaYrVJZWx`?Xz0ShOX~aX7>lH2P3s*-?h_(XZIfpvac~B%J?Zl+75VQOtnKmXh|L~zF;knl6+VLvGdbm>?8=NW>jqi8+l)z&lEk`h>1&O;*AvY&{kL}0L3naRGX=2 zOO!We1JMH$*TDqt*9!9gUrhhVaCv%=Ux)5ZxPBhf98;xrDK`14mbHAKSLHoVdneQuf)V>e;rHjIBowTr$u)f{%u$Ab=Xd>j+xo9BgEZyC?|O2tidrfM2;BrRYwS}0 z2n7G5x_b}!kHQe(@k7jIvPnk4x zDSe{GfM0%;kG&Ce33eLq9CVSqC@GGTIEToe5Z@Sl&cK37LpFa1Z~C;wVmb^vGKsYf zpO0ou#E(%Ml=Fzh)9JtrPnMS+JHiYi=UbY(Rx+=d5jvW}6O<;cmm&f`3Xi%EDya-Q z-pwX#NmBt~X1r(wk`{M*mRl*8YLiu4q+WdGSPAx9f>5wHF5H*3G;^%^M3l~)-O5SB^7*isRv4aR39#rSN8M9I1~sIcbSbcWW3k=0`BG+QB>0>i1Bu?k zA-ET>2-;I{1i;30K5q-1Pk3cl9K>;IJGt{F@`qjHdFddcmQtNy+-9TS z2;!WU)iS}p_R{3mwiN5Z!lO-=kQLdjjH|#Jr9)37H?PeP=~iN0o}WuNeB`jBINEx; zCDT2y_To{Il?@ZIK>WZP@KP%ppOxaX;2BJXEm(PFRVbo_1a)}C*YD|pmRwp7MP zf+?PFl~8(|P0;ba<`qM=v_rtxdrz~`l|Zp&_Ri?e4yEgix9lU<6j4p-J(fEV0?xKj zXIlId97B!bRBDT5IS(NYUsx6&Y(}+Js;*A$2CX?E=nAQMn#5W+=;sF;&alTlG7io< zETc&TA3#|x=V8ITrys0A6DC!zgu0H#2cpWO^OmYgLR5N-Zi`!BPw+T&<`(XP*)((> z6`&qWQXX&>3e1!UFiLgCx#=fJCh^W%Ns{|HedPJvVny?Bg8m^B{OmpcWElxYc@nBd zc(Qod3CcvYV$o8_B*!r}8B`ax$PLDo#P0lngQ$y{; zZ!0f@Hx8R$7Jqh2mnwvBa@&fXUn+4FFZym{o#`b_)!RAk;X1RDQM)(e_%-RVM@waw zuXhj&2-o=ao@S?fj}&tpY_xOlng>68ceQP%F%~vinKj<2?YhK&vwn7GyFs(;efX%- z)VtdpS9`Fcy6eRIx#?o3rQV739t+#N4K2WPN(go=GRWN zBpxg=VM6v*!y>AY$p|sk$oZBT#)$a7$)Uh+s~`_s6Z%ERvK|D8bDO^u9R1NtN;X~2 zRCQ3GiKTNeGP$uU-^VJj@51@+jJt?v)M3Xb*K_A@#`6zne0+~TURji3zNjTL-dT1C zgPb{73xQ$xo-U|OBTChIfx<7Lg z>PZRS!tn)s`jWXvSTwx*)FrTT|0YOj(4wV|!Mg`Pr=^hhbVMiF z{E~)fZj_&(pctUQ=4b~xz!NF98JRgCVM>`x{;U2hU)Nk>K%2xnH^S(I zsjFnPsI|sohev#-X(4-)_vv*#w^Fx$Cyfm73`h3)u7xH|>?0UaZm~^GO>L3-8$TGe z$qMJD{a0Xt(p}P~NfpQlcWWzLKg3n^WR<%#LzUk7LFku4A`4 z((3-B&T60V1Crufq`CL0ZPaUn%o9tR+DGxeEO84JTNk2?8N7qPKT&vO{inG8wFwXN zpBj*WV)$%a7KDOY#DILrxTl;CH_@HB0mvsHLu&#hT_!q1UC!9PgJ9NN5Ubm7f!BDi zlb8F0K!z^(PRaE}Nv-(bhwJisyTLt#$D2zuH;&*lkZ?rJK(*lcfV8|_d9m%1n*7Ra zovCRJy5=D>(Tk3dQl$QD<7`udRc5j&x#T$XeJ7B#Y0cbpJ>>J`6fTVd8KPaBkQjpm zYhaogWRIc(mLWA#p#6!CvbS}wIY_&-L~PV$=T?@jU_GWfzk7D=kmZiF_Yz00wcZ~M z6u4n%ndZtcSjw1lP%7Yp&|r4W?~O88V;fKvk|{sRO>0CztJ&b%+k_$i+f z)w`StfBN}$z~e$tn*HJ=X|N3BQDDm{n>01YUGK;#h0Lzdg(Yg^ExjP5X2vOASd`x7 zFjrG(;p6{>T}sxPlG^2>Qi39gvc7z3InTWb${cT1pRm3%9eJfOkrioId3edGI)H6M zu94o?*E%)Zh0QT|#T>3eaZAPUs>D)FJK+ z1K5Ug!UB7=`Hjk~2~tL>riH26WyDZsIXu)yIFYJ}w#W@9xG=OAl&uYo(L^qg-gVo5 z7%Y*)$bA?6qJdCVT}2ih7oyJAF~%iJk+T+*X5OhfFJ{9szG0#OCJe{oUq-=ny^Le&Hv?#zrj;#Xd*|PN+HGGR_0zZ5 zkUzEzdm8d>!Ly~gYCq6WMbn(O@BUfT-}&0$;rS2EVj+Uu3|uIR3Wzd}My}S>;6U%R zSwv!x@---fQw2S`PmNq`2<1fON0ROO`0R^e-Fz-U+EuiudZ%#iH^Qe>J?X9cZy`G* z=6fvjw`U)jkQOU999rJ;qZ}1{*~k1QgjVMs84+2)$bJNTMyl#)oIGIBg7@~ z$2#NN7yeDgL7`qBHFud3J$D#=dZId9Um}Gr4=*7d2o^|-6x+|eE+rzw?%c?8cD0F% zQdzTOeCF@(tx4=KEwmbHbtBE`lkl2#sP2;}TSn;eY}3^DW34--bMFhe-UZrE)|D39 z?xV&L4gH6lv(F00K(Wkm=mtEaH*+L)o1EORRL17FkgQA62-gz#5$D|(B|^D{Y3=4( zx*2TM-KPxBk^W=5{dJG^UG)6q_0#7JEgGHTuP>~^L{q4t^Shn3)L4W<26oo;%eu*I0hb^K!24j&f&$wTTj159^15wwFYKA zexpF>`rdbGzv+rw0!K)fd7oo=N%Mf+w34S9qo$m&c=$Pq8^-n$4%+ljvU3O9X_RXB zy$>?QJ9?G5PGPA}6ghM$X+Bgm#EVtFxR#fK{C+=3@K>F7Ztm{^{v#g!o;dz9f7Jo;ba!4ZD`d0zV8@#KGj2S@x%y!_WcSl@lu{v2=qZ`=1fkABai0Adi0CzP2w zflWY0LJ_%SWoi<nff|U6sZpfN+mzLySdSS3_a9e-DcCq%Np7(Gch-d}l*j{h+ z^?q5MQ#siyIBY6nEsGjU94t6p@oqZLpwTU|myAShLLW3+G4>!iR4JR~7FPmsmdMqx z9^~}4Mp2TWW^ltUV=ZONYhF?wKshzz`&Z}Gto91_IhBdzxD&-(v2Zk5frQiSS%Wna zW-wd1Sq&sE+j@ZNP^^wgkrw01^&`yD}dH0XA@YI2g^f!UJ*d zLjoa;29PjyLGuPa9gmrROUO^^$>rrWU9@jxddV6|i-B>r)g09Ej&2iWD6Y5SDO{L%d%FFRUMJ)J!ngOY8q)tGN*)jA(X9xC9MM>D&tyg* zs0cO37MQ>Y945fb(4cD$d>5+^crQ3Xx`D8Pt<|-yM^_?<&~PF;`gvvP=i6%nKc~0* z0>`)ur?R0H$d1KG^Ikah+bym8RTVTMZ^

u70|?mVYVjTs!NVXM8Q&bvqX2MrA#F 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

o*I~b8TR< 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
h1p8^EqMI_>;-aL(5jFxvHd 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�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 literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_files/test.mp3 b/packages/markitdown/tests/test_files/test.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b13ff887059e8e6f3be8bdd2f41801f33c26b28c GIT binary patch literal 156258 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

3wkS=0Vp#C# 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

Y^fqur(hap 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 H?%aO>rT}kC literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_files/test.pptx b/packages/markitdown/tests/test_files/test.pptx index e6d16f3cbb7836ff92b701b5fa61d92c165c50ab..fb663021a11a7766e74a83ebe5c7871fd369a760 100644 GIT binary patch delta 146527 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%0D&zLb-v9H3g!oe9_6vD6u?{c!_V?&~dqgve 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

    hvzWHFPqQR8ZS%}qJ- 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

    #yiOmSA!tfw?i;uU%Aal% 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

    Test

    " + result = markitdown.convert_stream(io.BytesIO(input_data)) + assert "# Test" in result.text_content + # Test input with leading blank characters input_data = b" \n\n\n

    Test

    " result = markitdown.convert_stream(io.BytesIO(input_data)) assert "# Test" in result.text_content +def test_markitdown_streams() -> None: + markitdown = MarkItDown() + + # Test PDF processing + with open(os.path.join(TEST_FILES_DIR, "test.pdf"), "rb") as f: + result = markitdown.convert(f, file_extension=".pdf") + validate_strings(result, PDF_TEST_STRINGS) + + # Test XLSX processing + with open(os.path.join(TEST_FILES_DIR, "test.xlsx"), "rb") as f: + result = markitdown.convert(f, file_extension=".xlsx") + validate_strings(result, XLSX_TEST_STRINGS) + + # Test XLS processing + with open(os.path.join(TEST_FILES_DIR, "test.xls"), "rb") as f: + result = markitdown.convert(f, file_extension=".xls") + for test_string in XLS_TEST_STRINGS: + text_content = result.text_content.replace("\\", "") + assert test_string in text_content + + # Test DOCX processing + with open(os.path.join(TEST_FILES_DIR, "test.docx"), "rb") as f: + result = markitdown.convert(f, file_extension=".docx") + validate_strings(result, DOCX_TEST_STRINGS) + + # Test DOCX processing, with comments + with open(os.path.join(TEST_FILES_DIR, "test_with_comment.docx"), "rb") as f: + result = markitdown.convert( + f, + file_extension=".docx", + style_map="comment-reference => ", + ) + validate_strings(result, DOCX_COMMENT_TEST_STRINGS) + + # Test DOCX processing, with comments and setting style_map on init + markitdown_with_style_map = MarkItDown(style_map="comment-reference => ") + with open(os.path.join(TEST_FILES_DIR, "test_with_comment.docx"), "rb") as f: + result = markitdown_with_style_map.convert(f, file_extension=".docx") + validate_strings(result, DOCX_COMMENT_TEST_STRINGS) + + # Test PPTX processing + with open(os.path.join(TEST_FILES_DIR, "test.pptx"), "rb") as f: + result = markitdown.convert(f, file_extension=".pptx") + validate_strings(result, PPTX_TEST_STRINGS) + + # Test HTML processing + with open(os.path.join(TEST_FILES_DIR, "test_blog.html"), "rb") as f: + result = markitdown.convert(f, file_extension=".html", url=BLOG_TEST_URL) + validate_strings(result, BLOG_TEST_STRINGS) + + # Test Wikipedia processing + with open(os.path.join(TEST_FILES_DIR, "test_wikipedia.html"), "rb") as f: + result = markitdown.convert(f, file_extension=".html", url=WIKIPEDIA_TEST_URL) + text_content = result.text_content.replace("\\", "") + validate_strings(result, WIKIPEDIA_TEST_STRINGS, WIKIPEDIA_TEST_EXCLUDES) + + # Test Bing processing + with open(os.path.join(TEST_FILES_DIR, "test_serp.html"), "rb") as f: + result = markitdown.convert(f, file_extension=".html", url=SERP_TEST_URL) + text_content = result.text_content.replace("\\", "") + validate_strings(result, SERP_TEST_STRINGS, SERP_TEST_EXCLUDES) + + # Test RSS processing + with open(os.path.join(TEST_FILES_DIR, "test_rss.xml"), "rb") as f: + result = markitdown.convert(f, file_extension=".xml") + text_content = result.text_content.replace("\\", "") + for test_string in RSS_TEST_STRINGS: + assert test_string in text_content + + # Test MSG (Outlook email) processing + with open(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg"), "rb") as f: + result = markitdown.convert(f, file_extension=".msg") + validate_strings(result, MSG_TEST_STRINGS) + + # Test JSON processing + with open(os.path.join(TEST_FILES_DIR, "test.json"), "rb") as f: + result = markitdown.convert(f, file_extension=".json") + validate_strings(result, JSON_TEST_STRINGS) + + +@pytest.mark.skipif( + skip_remote, + reason="do not run remotely run speech transcription tests", +) +def test_speech_transcription() -> None: + markitdown = MarkItDown() + + # Test WAV files, MP3 and M4A files + for file_name in ["test.wav", "test.mp3", "test.m4a"]: + result = markitdown.convert(os.path.join(TEST_FILES_DIR, file_name)) + result_lower = result.text_content.lower() + assert ( + ("1" in result_lower or "one" in result_lower) + and ("2" in result_lower or "two" in result_lower) + and ("3" in result_lower or "three" in result_lower) + and ("4" in result_lower or "four" in result_lower) + and ("5" in result_lower or "five" in result_lower) + ) + + def test_exceptions() -> None: # Check that an exception is raised when trying to convert an unsupported format markitdown = MarkItDown() @@ -295,17 +520,20 @@ def test_markitdown_exiftool() -> None: # Test the automatic discovery of exiftool throws a warning # and is disabled try: - with catch_warnings(record=True) as w: + warnings.simplefilter("default") + with warnings.catch_warnings(record=True) as w: markitdown = MarkItDown() result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.jpg")) assert len(w) == 1 assert w[0].category is DeprecationWarning assert result.text_content.strip() == "" finally: - resetwarnings() + warnings.resetwarnings() + + which_exiftool = shutil.which("exiftool") + assert which_exiftool is not None # Test explicitly setting the location of exiftool - which_exiftool = shutil.which("exiftool") markitdown = MarkItDown(exiftool_path=which_exiftool) result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.jpg")) for key in JPG_TEST_EXIFTOOL: @@ -320,6 +548,12 @@ def test_markitdown_exiftool() -> None: target = f"{key}: {JPG_TEST_EXIFTOOL[key]}" assert target in result.text_content + # Test some other media types + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.mp3")) + for key in MP3_TEST_EXIFTOOL: + target = f"{key}: {MP3_TEST_EXIFTOOL[key]}" + assert target in result.text_content + @pytest.mark.skipif( skip_llm, @@ -330,7 +564,6 @@ def test_markitdown_llm() -> None: markitdown = MarkItDown(llm_client=client, llm_model="gpt-4o") result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_llm.jpg")) - for test_string in LLM_TEST_STRINGS: assert test_string in result.text_content @@ -339,12 +572,24 @@ def test_markitdown_llm() -> None: for test_string in ["red", "circle", "blue", "square"]: assert test_string in result.text_content.lower() + # Images embedded in PPTX files + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.pptx")) + # LLM Captions are included + for test_string in LLM_TEST_STRINGS: + assert test_string in result.text_content + # Standard alt text is included + validate_strings(result, PPTX_TEST_STRINGS) + if __name__ == "__main__": """Runs this file's tests from the command line.""" + test_stream_info_operations() + test_stream_info_guesses() test_markitdown_remote() test_markitdown_local() + test_markitdown_streams() + test_speech_transcription() test_exceptions() test_markitdown_exiftool() - # test_markitdown_llm() + test_markitdown_llm() print("All tests passed!") From 70e9f8c3c041e35574dab45c648e655bd363bb75 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 5 Mar 2025 21:26:06 -0800 Subject: [PATCH 35/75] Bump version. (#1094) --- packages/markitdown-sample-plugin/pyproject.toml | 2 +- .../src/markitdown_sample_plugin/_plugin.py | 5 ----- packages/markitdown/src/markitdown/__about__.py | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/markitdown-sample-plugin/pyproject.toml b/packages/markitdown-sample-plugin/pyproject.toml index d8668aa..a12b9aa 100644 --- a/packages/markitdown-sample-plugin/pyproject.toml +++ b/packages/markitdown-sample-plugin/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "markitdown>=0.0.2a2", + "markitdown>=0.2.0a1", "striprtf", ] diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py index 1362818..1ca00cc 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/_plugin.py @@ -36,11 +36,6 @@ class RtfConverter(DocumentConverter): Converts an RTF file to in the simplest possible way. """ - def __init__( - self, priority: float = DocumentConverter.PRIORITY_SPECIFIC_FILE_FORMAT - ): - super().__init__(priority=priority) - def accepts( self, file_stream: BinaryIO, diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index 4ebb498..ca1bba5 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.0.2a2" +__version__ = "0.2.0a1" From 784c293579fc72203cdfabeeb6b54139fd22a89d Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Wed, 5 Mar 2025 21:55:20 -0800 Subject: [PATCH 36/75] Bump plugin version. --- .../src/markitdown_sample_plugin/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py index a365900..ca1bba5 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.0.1a3" +__version__ = "0.2.0a1" From 9380112892770f61402e4b539749e0fea03b985e Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 5 Mar 2025 22:24:08 -0800 Subject: [PATCH 37/75] Fixed loading of plugins. (#1096) --- packages/markitdown-sample-plugin/pyproject.toml | 2 +- .../src/markitdown_sample_plugin/__about__.py | 2 +- packages/markitdown/src/markitdown/__about__.py | 2 +- packages/markitdown/src/markitdown/_markitdown.py | 8 +++++--- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/markitdown-sample-plugin/pyproject.toml b/packages/markitdown-sample-plugin/pyproject.toml index a12b9aa..400cc4f 100644 --- a/packages/markitdown-sample-plugin/pyproject.toml +++ b/packages/markitdown-sample-plugin/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "markitdown>=0.2.0a1", + "markitdown>=0.2.0a2", "striprtf", ] diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py index ca1bba5..f691daf 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.2.0a1" +__version__ = "0.2.0a2" diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index ca1bba5..f691daf 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.2.0a1" +__version__ = "0.2.0a2" diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 6086eb9..d88bb73 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -58,10 +58,10 @@ PRIORITY_GENERIC_FILE_FORMAT = ( ) -_plugins: List[Any] = [] +_plugins: Union[None, List[Any]] = None # If None, plugins have not been loaded yet. -def _load_plugins() -> List[Any]: +def _load_plugins() -> Union[None, List[Any]]: """Lazy load plugins, exiting early if already loaded.""" global _plugins @@ -186,7 +186,9 @@ class MarkItDown: """ if not self._plugins_enabled: # Load plugins - for plugin in _load_plugins(): + plugins = _load_plugins() + assert plugins is not None + for plugin in plugins: try: plugin.register_converters(self, **kwargs) except Exception: From 6bedf6d950719a7432ba6b7c34c946ba70ddfb76 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 5 Mar 2025 22:52:52 -0800 Subject: [PATCH 38/75] Fixed version. (#1097) --- packages/markitdown-sample-plugin/pyproject.toml | 2 +- .../src/markitdown_sample_plugin/__about__.py | 2 +- packages/markitdown/src/markitdown/__about__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/markitdown-sample-plugin/pyproject.toml b/packages/markitdown-sample-plugin/pyproject.toml index 400cc4f..4721036 100644 --- a/packages/markitdown-sample-plugin/pyproject.toml +++ b/packages/markitdown-sample-plugin/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "markitdown>=0.2.0a2", + "markitdown>=0.1.0a1", "striprtf", ] diff --git a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py index f691daf..91bf07b 100644 --- a/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py +++ b/packages/markitdown-sample-plugin/src/markitdown_sample_plugin/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.2.0a2" +__version__ = "0.1.0a1" diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index f691daf..91bf07b 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.2.0a2" +__version__ = "0.1.0a1" From 00a65e8f8bea17727cecf10eb7525ba69f48dc81 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Wed, 5 Mar 2025 23:10:21 -0800 Subject: [PATCH 39/75] Fixed version in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f9ef70..ac61512 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Built by AutoGen Team](https://img.shields.io/badge/Built%20by-AutoGen%20Team-blue)](https://github.com/microsoft/autogen) > [!IMPORTANT] -> Breaking changes between 0.0.1 to 0.0.2: +> 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. > * 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. From 80baa5db18e6133965dc9ac47bbebf734808ed7e Mon Sep 17 00:00:00 2001 From: Andrea Pietrobon <32714047+Piero24@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:21:10 +0100 Subject: [PATCH 40/75] fix(README): correct pip install command formatting (#1090) Added missing quotes around `markitdown[all]` in the installation command to ensure proper package resolution by pip. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac61512..0aa788c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > [!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. +> * Dependencies are now organized into optional feature-groups (further details below). Use `pip install 'markitdown[all]~=0.1.0a1'` to have backward-compatible behavior. > * 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. 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. @@ -36,7 +36,7 @@ are also highly token-efficient. ## Installation -To install MarkItDown, use pip: `pip install markitdown[all]`. Alternatively, you can install it from the source: +To install MarkItDown, use pip: `pip install 'markitdown[all]~=0.1.0a1'`. Alternatively, you can install it from the source: ```bash git clone git@github.com:microsoft/markitdown.git From 36c4bc9ec321f176fdd1ffeeb6e37e98b367cc46 Mon Sep 17 00:00:00 2001 From: scalabreseGD <47219719+scalabreseGD@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:25:37 +0100 Subject: [PATCH 41/75] Fixed deepcopy failure when passing llm_client (#1089) Co-authored-by: afourney --- packages/markitdown/src/markitdown/_markitdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index d88bb73..fd2b85f 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -455,7 +455,7 @@ class MarkItDown: cur_pos == file_stream.tell() ), f"File stream position should NOT change between guess iterations" - _kwargs = copy.deepcopy(kwargs) + _kwargs = {k:v for k,v in kwargs.items()} # Copy any additional global options if "llm_client" not in _kwargs and self._llm_client is not None: From 82d84e3edd6ecf319e83362729f68d4a4e511a3c Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 5 Mar 2025 23:30:29 -0800 Subject: [PATCH 42/75] Fixed formatting. (#1098) --- packages/markitdown/src/markitdown/_markitdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index fd2b85f..f32b236 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -455,7 +455,7 @@ class MarkItDown: cur_pos == file_stream.tell() ), f"File stream position should NOT change between guess iterations" - _kwargs = {k:v for k,v in kwargs.items()} + _kwargs = {k: v for k, v in kwargs.items()} # Copy any additional global options if "llm_client" not in _kwargs and self._llm_client is not None: From 0229ff6cb75aa63e5ba6582766d0e5b9cba1fdfa Mon Sep 17 00:00:00 2001 From: Richard Ye <33409792+richardye101@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:45:14 -0500 Subject: [PATCH 43/75] feat: sort pptx shapes to be parsed in top-to-bottom, left-to-right order (#1104) * Sort PPTX shapes to be read in top-to-bottom, left-to-right order Referenced from https://github.com/ssine/pptx2md/blob/39bef65b312035baeade932aad8d221e37daae5f/pptx2md/parser.py#L249 * Update README.md * Fixed formatting. * Added missing import --- README.md | 2 +- .../src/markitdown/converters/_pptx_converter.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0aa788c..40f4b82 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ MarkItDown is a lightweight Python utility for converting various files to Markd At present, MarkItDown supports: - PDF -- PowerPoint +- PowerPoint (reading in top-to-bottom, left-to-right order) - Word - Excel - Images (EXIF metadata and OCR) diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index bea1226..bcde6c9 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -6,6 +6,7 @@ import re import html from typing import BinaryIO, Any +from operator import attrgetter from ._html_converter import HtmlConverter from ._llm_caption import llm_caption @@ -160,10 +161,12 @@ class PptxConverter(DocumentConverter): # Group Shapes if shape.shape_type == pptx.enum.shapes.MSO_SHAPE_TYPE.GROUP: - for subshape in shape.shapes: + sorted_shapes = sorted(shape.shapes, key=attrgetter("top", "left")) + for subshape in sorted_shapes: get_shape_content(subshape, **kwargs) - for shape in slide.shapes: + sorted_shapes = sorted(slide.shapes, key=attrgetter("top", "left")) + for shape in sorted_shapes: get_shape_content(shape, **kwargs) md_content = md_content.strip() From 515fa854bfcbd1b94d999690ef8945c8b96c75cb Mon Sep 17 00:00:00 2001 From: Sebastian Yaghoubi <79172513+syaghoubi00@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:07:40 -0800 Subject: [PATCH 44/75] feat(docker): improve dockerfile build (#220) * refactor(docker): remove unnecessary root user The USER root directive isn't needed directly after FROM Signed-off-by: Sebastian Yaghoubi * fix(docker): use generic nobody nogroup default instead of uid gid Signed-off-by: Sebastian Yaghoubi * fix(docker): build app from source locally instead of installing package Signed-off-by: Sebastian Yaghoubi * fix(docker): use correct files in dockerignore Signed-off-by: Sebastian Yaghoubi * chore(docker): dont install recommended packages with git Signed-off-by: Sebastian Yaghoubi * fix(docker): run apt as non-interactive Signed-off-by: Sebastian Yaghoubi * Update Dockerfile to new package structure, and fix streaming bugs. --------- Signed-off-by: Sebastian Yaghoubi Co-authored-by: afourney --- .dockerignore | 3 +- Dockerfile | 30 ++++++++++++------- .../markitdown/src/markitdown/_markitdown.py | 11 +++++++ .../converters/_outlook_msg_converter.py | 3 +- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.dockerignore b/.dockerignore index f59ec20..319b932 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -* \ No newline at end of file +* +!packages/ diff --git a/Dockerfile b/Dockerfile index 0072d9e..c65bf9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,32 @@ FROM python:3.13-slim-bullseye -USER root - -ARG INSTALL_GIT=false -RUN if [ "$INSTALL_GIT" = "true" ]; then \ - apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*; \ - fi +ENV DEBIAN_FRONTEND=noninteractive +ENV EXIFTOOL_PATH=/usr/bin/exiftool +ENV FFMPEG_PATH=/usr/bin/ffmpeg # Runtime dependency RUN apt-get update && apt-get install -y --no-install-recommends \ ffmpeg \ - && rm -rf /var/lib/apt/lists/* + exiftool -RUN pip install markitdown +ARG INSTALL_GIT=false +RUN if [ "$INSTALL_GIT" = "true" ]; then \ + apt-get install -y --no-install-recommends \ + git; \ + fi + +# Cleanup +RUN rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY . /app +RUN pip --no-cache-dir install \ + /app/packages/markitdown[all] \ + /app/packages/markitdown-sample-plugin # Default USERID and GROUPID -ARG USERID=10000 -ARG GROUPID=10000 +ARG USERID=nobody +ARG GROUPID=nogroup USER $USERID:$GROUPID diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index f32b236..04015d7 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -327,6 +327,17 @@ class MarkItDown: elif base_guess.extension is not None: placeholder_filename = "placeholder" + base_guess.extension + # Check if we have a seekable stream. If not, load the entire stream into memory. + if not stream.seekable(): + buffer = io.BytesIO() + while True: + chunk = stream.read(4096) + if not chunk: + break + buffer.write(chunk) + buffer.seek(0) + stream = buffer + # Add guesses based on stream content for guess in _guess_stream_info_from_stream( file_stream=stream, filename_hint=placeholder_filename diff --git a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py index 8a61b0c..8e20dc5 100644 --- a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py +++ b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py @@ -7,6 +7,7 @@ from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE # Try loading optional (but in this case, required) dependencies # Save reporting of any exceptions for later _dependency_exc_info = None +olefile = None try: import olefile except ImportError: @@ -48,7 +49,7 @@ class OutlookMsgConverter(DocumentConverter): # Brute force, check if we have an OLE file cur_pos = file_stream.tell() try: - if not olefile.isOleFile(file_stream): + if olefile and not olefile.isOleFile(file_stream): return False finally: file_stream.seek(cur_pos) From 99d8e562db89ee00c1fb09598132729d37dcf2e3 Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 7 Mar 2025 21:47:20 -0800 Subject: [PATCH 45/75] Fix exiftool in well-known paths. (#1106) --- .../markitdown/src/markitdown/_markitdown.py | 22 +++++++++++++++++++ .../src/markitdown/converters/_exiftool.py | 20 +++++------------ packages/markitdown/tests/test_markitdown.py | 15 ------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 04015d7..9aaa4cf 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -3,6 +3,7 @@ import mimetypes import os import re import sys +import shutil import tempfile import warnings import traceback @@ -138,9 +139,30 @@ class MarkItDown: self._llm_model = kwargs.get("llm_model") self._exiftool_path = kwargs.get("exiftool_path") self._style_map = kwargs.get("style_map") + if self._exiftool_path is None: self._exiftool_path = os.getenv("EXIFTOOL_PATH") + # Still none? Check well-known paths + if self._exiftool_path is None: + candidate = shutil.which("exiftool") + if candidate: + candidate = os.path.abspath(candidate) + if any( + d == os.path.dirname(candidate) + for d in [ + "/usr/bin", + "/usr/local/bin", + "/opt", + "/opt/bin", + "/opt/local/bin", + "/opt/homebrew/bin" "C:\\Windows\\System32", + "C:\\Program Files", + "C:\\Program Files (x86)", + ] + ): + self._exiftool_path = candidate + # Register converters for successful browsing operations # Later registrations are tried first / take higher priority than earlier registrations # To this end, the most specific converters should appear below the most generic converters diff --git a/packages/markitdown/src/markitdown/converters/_exiftool.py b/packages/markitdown/src/markitdown/converters/_exiftool.py index 5a316f0..43c100f 100644 --- a/packages/markitdown/src/markitdown/converters/_exiftool.py +++ b/packages/markitdown/src/markitdown/converters/_exiftool.py @@ -5,26 +5,16 @@ import sys import shutil import os import warnings -from typing import BinaryIO, Optional, Any +from typing import BinaryIO, Any, Union def exiftool_metadata( - file_stream: BinaryIO, *, exiftool_path: Optional[str] = None + file_stream: BinaryIO, + *, + exiftool_path: Union[str, None], ) -> Any: # Need a better type for json data - # Check if we have a valid pointer to exiftool + # Nothing to do if not exiftool_path: - which_exiftool = shutil.which("exiftool") - if which_exiftool: - warnings.warn( - f"""Implicit discovery of 'exiftool' is disabled. If you would like to continue to use exiftool in MarkItDown, please set the exiftool_path parameter in the MarkItDown consructor. E.g., - - md = MarkItDown(exiftool_path="{which_exiftool}") - -This warning will be removed in future releases. -""", - DeprecationWarning, - ) - # Nothing to do return {} # Run exiftool diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py index 8c34da0..25e5e33 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -7,8 +7,6 @@ import openai import pytest import requests -import warnings - from markitdown import ( MarkItDown, UnsupportedFormatException, @@ -517,19 +515,6 @@ def test_exceptions() -> None: reason="do not run if exiftool is not installed", ) def test_markitdown_exiftool() -> None: - # Test the automatic discovery of exiftool throws a warning - # and is disabled - try: - warnings.simplefilter("default") - with warnings.catch_warnings(record=True) as w: - markitdown = MarkItDown() - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.jpg")) - assert len(w) == 1 - assert w[0].category is DeprecationWarning - assert result.text_content.strip() == "" - finally: - warnings.resetwarnings() - which_exiftool = shutil.which("exiftool") assert which_exiftool is not None From 2405f201afe5d8f05373ba557b9bd7c1e29f1d82 Mon Sep 17 00:00:00 2001 From: Mohit Agarwal Date: Sun, 9 Mar 2025 09:02:44 +0530 Subject: [PATCH 46/75] fix typo in well-known path list (#1109) --- packages/markitdown/src/markitdown/_markitdown.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 9aaa4cf..079b65a 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -156,7 +156,8 @@ class MarkItDown: "/opt", "/opt/bin", "/opt/local/bin", - "/opt/homebrew/bin" "C:\\Windows\\System32", + "/opt/homebrew/bin", + "C:\\Windows\\System32", "C:\\Program Files", "C:\\Program Files (x86)", ] From 8e73a325c60cc1cc3650fc38487d135e70b9da5e Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 10 Mar 2025 12:49:52 -0700 Subject: [PATCH 47/75] Switch from puremagic to magika. (#1108) --- packages/markitdown/pyproject.toml | 3 +- .../markitdown/src/markitdown/_markitdown.py | 3 - .../markitdown/src/markitdown/_stream_info.py | 72 ++++++------------- 3 files changed, 22 insertions(+), 56 deletions(-) diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index d0f515e..1421852 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -27,8 +27,7 @@ dependencies = [ "beautifulsoup4", "requests", "markdownify", - "puremagic", - "pathvalidate", + "magika>=0.6.0rc1", "charset-normalizer", ] diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 079b65a..6a6957d 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -14,9 +14,6 @@ from typing import Any, List, Optional, Union, BinaryIO from pathlib import Path from urllib.parse import urlparse from warnings import warn - -# File-format detection -import puremagic import requests from ._stream_info import StreamInfo, _guess_stream_info_from_stream diff --git a/packages/markitdown/src/markitdown/_stream_info.py b/packages/markitdown/src/markitdown/_stream_info.py index 1eaa4d2..12c50f1 100644 --- a/packages/markitdown/src/markitdown/_stream_info.py +++ b/packages/markitdown/src/markitdown/_stream_info.py @@ -1,14 +1,10 @@ -import puremagic import mimetypes import os from dataclasses import dataclass, asdict from typing import Optional, BinaryIO, List, TypeVar, Type +from magika import Magika -# Mimetype substitutions table -MIMETYPE_SUBSTITUTIONS = { - "application/excel": "application/vnd.ms-excel", - "application/mspowerpoint": "application/vnd.ms-powerpoint", -} +magika = Magika() @dataclass(kw_only=True, frozen=True) @@ -59,6 +55,25 @@ def _guess_stream_info_from_stream( """ guesses: List[StreamInfo] = [] + # Call magika to guess from the stream + cur_pos = file_stream.tell() + try: + result = magika.identify_bytes(file_stream.read()) + if result.status == "ok" and result.prediction.output.label != "unknown": + extension = None + if len(result.prediction.output.extensions) > 0: + extension = result.prediction.output.extensions[0] + if extension and not extension.startswith("."): + extension = "." + extension + guesses.append( + StreamInfo( + mimetype=result.prediction.output.mime_type, + extension=extension, + ) + ) + finally: + file_stream.seek(cur_pos) + # Add a guess purely based on the filename hint if filename_hint: try: @@ -74,49 +89,4 @@ def _guess_stream_info_from_stream( ) ) - def _puremagic( - file_stream, filename_hint - ) -> List[puremagic.main.PureMagicWithConfidence]: - """Wrap guesses to handle exceptions.""" - try: - return puremagic.magic_stream(file_stream, filename=filename_hint) - except puremagic.main.PureError as e: - return [] - - cur_pos = file_stream.tell() - type_guesses = _puremagic(file_stream, filename_hint=filename_hint) - if len(type_guesses) == 0: - # Fix for: https://github.com/microsoft/markitdown/issues/222 - # If there are no guesses, then try again after trimming leading ASCII whitespaces. - # ASCII whitespace characters are those byte values in the sequence b' \t\n\r\x0b\f' - # (space, tab, newline, carriage return, vertical tab, form feed). - - # Eat all the leading whitespace - file_stream.seek(cur_pos) - while True: - char = file_stream.read(1) - if not char: # End of file - break - if not char.isspace(): - file_stream.seek(file_stream.tell() - 1) - break - - # Try again - type_guesses = _puremagic(file_stream, filename_hint=filename_hint) - file_stream.seek(cur_pos) - - # Convert and return the guesses - for guess in type_guesses: - kwargs: dict[str, str] = {} - if guess.extension: - kwargs["extension"] = guess.extension - if guess.mime_type: - kwargs["mimetype"] = MIMETYPE_SUBSTITUTIONS.get( - guess.mime_type, guess.mime_type - ) - if len(kwargs) > 0: - # We don't add the filename_hint, because sometimes it's just a placeholder, - # and, in any case, doesn't add new information. - guesses.append(StreamInfo(**kwargs)) - return guesses From 8f8e58c9bbff7968592f893cb378d4d86f95f028 Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 10 Mar 2025 15:30:44 -0700 Subject: [PATCH 48/75] Minimize guesses when guesses are compatible. (#1114) * Minimize guesses when guesses are compatible. --- .../markitdown/src/markitdown/_markitdown.py | 164 ++++++++++++------ .../markitdown/src/markitdown/_stream_info.py | 62 +------ packages/markitdown/tests/test_markitdown.py | 11 +- 3 files changed, 120 insertions(+), 117 deletions(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 6a6957d..c8cb684 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -15,8 +15,11 @@ from pathlib import Path from urllib.parse import urlparse from warnings import warn import requests +import magika +import charset_normalizer +import codecs -from ._stream_info import StreamInfo, _guess_stream_info_from_stream +from ._stream_info import StreamInfo from .converters import ( PlainTextConverter, @@ -107,6 +110,8 @@ class MarkItDown: else: self._requests_session = requests_session + self._magika = magika.Magika() + # TODO - remove these (see enable_builtins) self._llm_client: Any = None self._llm_model: Union[str | None] = None @@ -273,33 +278,28 @@ class MarkItDown: path = str(path) # Build a base StreamInfo object from which to start guesses - base_stream_info = StreamInfo( + base_guess = StreamInfo( local_path=path, extension=os.path.splitext(path)[1], filename=os.path.basename(path), ) - # Extend the base_stream_info with any additional info from the arguments + # Extend the base_guess with any additional info from the arguments if stream_info is not None: - base_stream_info = base_stream_info.copy_and_update(stream_info) + base_guess = base_guess.copy_and_update(stream_info) if file_extension is not None: # Deprecated -- use stream_info - base_stream_info = base_stream_info.copy_and_update( - extension=file_extension - ) + base_guess = base_guess.copy_and_update(extension=file_extension) if url is not None: # Deprecated -- use stream_info - base_stream_info = base_stream_info.copy_and_update(url=url) + base_guess = base_guess.copy_and_update(url=url) with open(path, "rb") as fh: - # Prepare a list of configurations to try, starting with the base_stream_info - guesses: List[StreamInfo] = [base_stream_info] - for guess in _guess_stream_info_from_stream( - file_stream=fh, filename_hint=path - ): - guesses.append(base_stream_info.copy_and_update(guess)) + guesses = self._get_stream_info_guesses( + file_stream=fh, base_guess=base_guess + ) return self._convert(file_stream=fh, stream_info_guesses=guesses, **kwargs) def convert_stream( @@ -332,21 +332,6 @@ class MarkItDown: assert base_guess is not None # for mypy base_guess = base_guess.copy_and_update(url=url) - # Append the base guess, if it's non-trivial - if base_guess is not None: - if base_guess.mimetype is not None or base_guess.extension is not None: - guesses.append(base_guess) - else: - # Create a base guess with no information - base_guess = StreamInfo() - - # Create a placeholder filename to help with guessing - placeholder_filename = None - if base_guess.filename is not None: - placeholder_filename = base_guess.filename - elif base_guess.extension is not None: - placeholder_filename = "placeholder" + base_guess.extension - # Check if we have a seekable stream. If not, load the entire stream into memory. if not stream.seekable(): buffer = io.BytesIO() @@ -359,12 +344,9 @@ class MarkItDown: stream = buffer # Add guesses based on stream content - for guess in _guess_stream_info_from_stream( - file_stream=stream, filename_hint=placeholder_filename - ): - guesses.append(base_guess.copy_and_update(guess)) - - # Perform the conversion + guesses = self._get_stream_info_guesses( + file_stream=stream, base_guess=base_guess or StreamInfo() + ) return self._convert(file_stream=stream, stream_info_guesses=guesses, **kwargs) def convert_url( @@ -435,31 +417,16 @@ class MarkItDown: # Deprecated -- use stream_info base_guess = base_guess.copy_and_update(url=url) - # Add the guess if its non-trivial - guesses: List[StreamInfo] = [] - if base_guess.mimetype is not None or base_guess.extension is not None: - guesses.append(base_guess) - # Read into BytesIO buffer = io.BytesIO() for chunk in response.iter_content(chunk_size=512): buffer.write(chunk) buffer.seek(0) - # Create a placeholder filename to help with guessing - placeholder_filename = None - if base_guess.filename is not None: - placeholder_filename = base_guess.filename - elif base_guess.extension is not None: - placeholder_filename = "placeholder" + base_guess.extension - - # Add guesses based on stream content - for guess in _guess_stream_info_from_stream( - file_stream=buffer, filename_hint=placeholder_filename - ): - guesses.append(base_guess.copy_and_update(guess)) - # Convert + guesses = self._get_stream_info_guesses( + file_stream=buffer, base_guess=base_guess + ) return self._convert(file_stream=buffer, stream_info_guesses=guesses, **kwargs) def _convert( @@ -593,3 +560,94 @@ class MarkItDown: self._converters.insert( 0, ConverterRegistration(converter=converter, priority=priority) ) + + def _get_stream_info_guesses( + self, file_stream: BinaryIO, base_guess: StreamInfo + ) -> List[StreamInfo]: + """ + Given a base guess, attempt to guess or expand on the stream info using the stream content (via magika). + """ + guesses: List[StreamInfo] = [] + + # Call magika to guess from the stream + cur_pos = file_stream.tell() + try: + stream_bytes = file_stream.read() + + result = self._magika.identify_bytes(stream_bytes) + if result.status == "ok" and result.prediction.output.label != "unknown": + # If it's text, also guess the charset + charset = None + if result.prediction.output.is_text: + charset_result = charset_normalizer.from_bytes(stream_bytes).best() + if charset_result is not None: + charset = self._normalize_charset(charset_result.encoding) + + # Normalize the first extension listed + guessed_extension = None + if len(result.prediction.output.extensions) > 0: + guessed_extension = "." + result.prediction.output.extensions[0] + + # Determine if the guess is compatible with the base guess + compatible = True + if ( + base_guess.mimetype is not None + and base_guess.mimetype != result.prediction.output.mime_type + ): + compatible = False + + if ( + base_guess.extension is not None + and base_guess.extension.lstrip(".") + not in result.prediction.output.extensions + ): + compatible = False + + if ( + base_guess.charset is not None + and self._normalize_charset(base_guess.charset) != charset + ): + compatible = False + + if compatible: + # Add the compatible base guess + guesses.append( + StreamInfo( + mimetype=base_guess.mimetype + or result.prediction.output.mime_type, + extension=base_guess.extension or guessed_extension, + charset=base_guess.charset or charset, + filename=base_guess.filename, + local_path=base_guess.local_path, + url=base_guess.url, + ) + ) + else: + # The magika guess was incompatible with the base guess, so add both guesses + guesses.append(base_guess) + guesses.append( + StreamInfo( + mimetype=result.prediction.output.mime_type, + extension=guessed_extension, + charset=charset, + filename=base_guess.filename, + local_path=base_guess.local_path, + url=base_guess.url, + ) + ) + else: + # There were no other guesses, so just add the base guess + guesses.append(base_guess) + finally: + file_stream.seek(cur_pos) + + return guesses + + def _normalize_charset(self, charset: str) -> str: + """ + Normalize a charset string to a canonical form. + """ + try: + return codecs.lookup(charset).name + except LookupError: + return charset diff --git a/packages/markitdown/src/markitdown/_stream_info.py b/packages/markitdown/src/markitdown/_stream_info.py index 12c50f1..84a1f64 100644 --- a/packages/markitdown/src/markitdown/_stream_info.py +++ b/packages/markitdown/src/markitdown/_stream_info.py @@ -1,10 +1,5 @@ -import mimetypes -import os from dataclasses import dataclass, asdict -from typing import Optional, BinaryIO, List, TypeVar, Type -from magika import Magika - -magika = Magika() +from typing import Optional @dataclass(kw_only=True, frozen=True) @@ -35,58 +30,3 @@ class StreamInfo: new_info.update(kwargs) return StreamInfo(**new_info) - - -# Behavior subject to change. -# Do not rely on this outside of this module. -def _guess_stream_info_from_stream( - file_stream: BinaryIO, - *, - filename_hint: Optional[str] = None, -) -> List[StreamInfo]: - """ - Guess StreamInfo properties (mostly mimetype and extension) from a stream. - - Args: - - stream: The stream to guess the StreamInfo from. - - filename_hint [Optional]: A filename hint to help with the guessing (may be a placeholder, and not actually be the file name) - - Returns a list of StreamInfo objects in order of confidence. - """ - guesses: List[StreamInfo] = [] - - # Call magika to guess from the stream - cur_pos = file_stream.tell() - try: - result = magika.identify_bytes(file_stream.read()) - if result.status == "ok" and result.prediction.output.label != "unknown": - extension = None - if len(result.prediction.output.extensions) > 0: - extension = result.prediction.output.extensions[0] - if extension and not extension.startswith("."): - extension = "." + extension - guesses.append( - StreamInfo( - mimetype=result.prediction.output.mime_type, - extension=extension, - ) - ) - finally: - file_stream.seek(cur_pos) - - # Add a guess purely based on the filename hint - if filename_hint: - try: - # Requires Python 3.13+ - mimetype, _ = mimetypes.guess_file_type(filename_hint) # type: ignore - except AttributeError: - mimetype, _ = mimetypes.guess_type(filename_hint) - - if mimetype: - guesses.append( - StreamInfo( - mimetype=mimetype, extension=os.path.splitext(filename_hint)[1] - ) - ) - - return guesses diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py index 25e5e33..f76ff8c 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -13,7 +13,6 @@ from markitdown import ( FileConversionException, StreamInfo, ) -from markitdown._stream_info import _guess_stream_info_from_stream skip_remote = ( True if os.environ.get("GITHUB_ACTIONS") else False @@ -265,10 +264,16 @@ def test_stream_info_guesses() -> None: (os.path.join(TEST_FILES_DIR, "test.xls"), "application/vnd.ms-excel"), ] + markitdown = MarkItDown() for file_path, expected_mimetype in test_tuples: with open(file_path, "rb") as f: - guesses = _guess_stream_info_from_stream( - f, filename_hint=os.path.basename(file_path) + guesses = markitdown._get_stream_info_guesses( + f, + StreamInfo( + filename=os.path.basename(file_path), + local_path=file_path, + extension=os.path.splitext(file_path)[1], + ), ) assert len(guesses) > 0 assert guesses[0].mimetype == expected_mimetype From 2e51ba22e7c7ff6259f13a53033b9addfe0f7012 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Mon, 10 Mar 2025 16:05:41 -0700 Subject: [PATCH 49/75] Enhance type guessing. --- .../markitdown/src/markitdown/_markitdown.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index c8cb684..825643c 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -569,6 +569,23 @@ class MarkItDown: """ guesses: List[StreamInfo] = [] + # Enhance the base guess with information based on the extension or mimetype + enhanced_guess = base_guess.copy_and_update() + + # If there's an extension and no mimetype, try to guess the mimetype + if base_guess.mimetype is None and base_guess.extension is not None: + _m, _ = mimetypes.guess_type( + "placeholder" + base_guess.extension, strict=False + ) + if _m is not None: + enhanced_guess = enhanced_guess.copy_and_update(mimetype=_m) + + # If there's a mimetype and no extension, try to guess the extension + if base_guess.mimetype is not None and base_guess.extension is None: + _e = mimetypes.guess_all_extensions(base_guess.mimetype, strict=False) + if len(_e) > 0: + enhanced_guess = enhanced_guess.copy_and_update(extension=_e[0]) + # Call magika to guess from the stream cur_pos = file_stream.tell() try: @@ -624,7 +641,7 @@ class MarkItDown: ) else: # The magika guess was incompatible with the base guess, so add both guesses - guesses.append(base_guess) + guesses.append(enhanced_guess) guesses.append( StreamInfo( mimetype=result.prediction.output.mime_type, @@ -637,7 +654,7 @@ class MarkItDown: ) else: # There were no other guesses, so just add the base guess - guesses.append(base_guess) + guesses.append(enhanced_guess) finally: file_stream.seek(cur_pos) From 2a2ccc86aa204cba0618aeb1f1c0ed5153133a6d Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Mon, 10 Mar 2025 16:17:41 -0700 Subject: [PATCH 50/75] Added mimetypes to _rss_converter --- packages/markitdown/src/markitdown/converters/_rss_converter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/markitdown/src/markitdown/converters/_rss_converter.py b/packages/markitdown/src/markitdown/converters/_rss_converter.py index dbafc1b..31e5ad5 100644 --- a/packages/markitdown/src/markitdown/converters/_rss_converter.py +++ b/packages/markitdown/src/markitdown/converters/_rss_converter.py @@ -8,7 +8,9 @@ from .._base_converter import DocumentConverter, DocumentConverterResult PRECISE_MIME_TYPE_PREFIXES = [ "application/rss", + "application/rss+xml", "application/atom", + "application/atom+xml", ] PRECISE_FILE_EXTENSIONS = [".rss", ".atom"] From af1be36e0c5589f17e38066718130fefaff6c05c Mon Sep 17 00:00:00 2001 From: afourney Date: Tue, 11 Mar 2025 13:16:33 -0700 Subject: [PATCH 51/75] Added CLI options for extension, mimetypes, and charset. (#1115) --- .../markitdown/src/markitdown/__main__.py | 77 +++++++++++++++++-- .../markitdown/src/markitdown/_markitdown.py | 6 +- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/packages/markitdown/src/markitdown/__main__.py b/packages/markitdown/src/markitdown/__main__.py index 6a24391..8266f5c 100644 --- a/packages/markitdown/src/markitdown/__main__.py +++ b/packages/markitdown/src/markitdown/__main__.py @@ -3,10 +3,11 @@ # SPDX-License-Identifier: MIT import argparse import sys +import codecs from textwrap import dedent from importlib.metadata import entry_points from .__about__ import __version__ -from ._markitdown import MarkItDown, DocumentConverterResult +from ._markitdown import MarkItDown, StreamInfo, DocumentConverterResult def main(): @@ -58,6 +59,24 @@ def main(): help="Output file name. If not provided, output is written to stdout.", ) + parser.add_argument( + "-x", + "--extension", + help="Provide a hint about the file extension (e.g., when reading from stdin).", + ) + + parser.add_argument( + "-m", + "--mime-type", + help="Provide a hint about the file's MIME type.", + ) + + parser.add_argument( + "-c", + "--charset", + help="Provide a hint about the file's charset (e.g, UTF-8).", + ) + parser.add_argument( "-d", "--use-docintel", @@ -88,6 +107,48 @@ def main(): parser.add_argument("filename", nargs="?") args = parser.parse_args() + # Parse the extension hint + extension_hint = args.extension + if extension_hint is not None: + extension_hint = extension_hint.strip().lower() + if len(extension_hint) > 0: + if not extension_hint.startswith("."): + extension_hint = "." + extension_hint + else: + extension_hint = None + + # Parse the mime type + mime_type_hint = args.mime_type + if mime_type_hint is not None: + mime_type_hint = mime_type_hint.strip() + if len(mime_type_hint) > 0: + if mime_type_hint.count("/") != 1: + _exit_with_error(f"Invalid MIME type: {mime_type_hint}") + else: + mime_type_hint = None + + # Parse the charset + charset_hint = args.charset + if charset_hint is not None: + charset_hint = charset_hint.strip() + if len(charset_hint) > 0: + try: + charset_hint = codecs.lookup(charset_hint).name + except LookupError: + _exit_with_error(f"Invalid charset: {charset_hint}") + else: + charset_hint = None + + stream_info: str | None = None + if ( + extension_hint is not None + or mime_type_hint is not None + or charset_hint is not None + ): + stream_info = StreamInfo( + extension=extension_hint, mimetype=mime_type_hint, charset=charset_hint + ) + if args.list_plugins: # List installed plugins, then exit print("Installed MarkItDown 3rd-party Plugins:\n") @@ -107,11 +168,12 @@ def main(): if args.use_docintel: if args.endpoint is None: - raise ValueError( + _exit_with_error( "Document Intelligence Endpoint is required when using Document Intelligence." ) elif args.filename is None: - raise ValueError("Filename is required when using Document Intelligence.") + _exit_with_error("Filename is required when using Document Intelligence.") + markitdown = MarkItDown( enable_plugins=args.use_plugins, docintel_endpoint=args.endpoint ) @@ -119,9 +181,9 @@ def main(): markitdown = MarkItDown(enable_plugins=args.use_plugins) if args.filename is None: - result = markitdown.convert_stream(sys.stdin.buffer) + result = markitdown.convert_stream(sys.stdin.buffer, stream_info=stream_info) else: - result = markitdown.convert(args.filename) + result = markitdown.convert(args.filename, stream_info=stream_info) _handle_output(args, result) @@ -135,5 +197,10 @@ def _handle_output(args, result: DocumentConverterResult): print(result.text_content) +def _exit_with_error(message: str): + print(message) + sys.exit(1) + + if __name__ == "__main__": main() diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 825643c..b116927 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -244,7 +244,7 @@ class MarkItDown: or source.startswith("https://") or source.startswith("file://") ): - return self.convert_url(source, **kwargs) + return self.convert_url(source, stream_info=stream_info, *kwargs) else: return self.convert_local(source, stream_info=stream_info, **kwargs) # Path object @@ -252,14 +252,14 @@ class MarkItDown: return self.convert_local(source, stream_info=stream_info, **kwargs) # Request response elif isinstance(source, requests.Response): - return self.convert_response(source, **kwargs) + return self.convert_response(source, stream_info=stream_info, **kwargs) # Binary stream elif ( hasattr(source, "read") and callable(source.read) and not isinstance(source, io.TextIOBase) ): - return self.convert_stream(source, **kwargs) + return self.convert_stream(source, stream_info=stream_info, **kwargs) else: raise TypeError( f"Invalid source type: {type(source)}. Expected str, requests.Response, BinaryIO." From 75140a90e2f78b44061c1798d7ece2995559bd9d Mon Sep 17 00:00:00 2001 From: yushihang Date: Thu, 13 Mar 2025 01:15:09 +0800 Subject: [PATCH 52/75] fix: correct f-string formatting in FileConversionException (#1121) --- packages/markitdown/src/markitdown/_exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/_exceptions.py b/packages/markitdown/src/markitdown/_exceptions.py index 93f8f0e..2f87ac8 100644 --- a/packages/markitdown/src/markitdown/_exceptions.py +++ b/packages/markitdown/src/markitdown/_exceptions.py @@ -69,7 +69,7 @@ class FileConversionException(MarkItDownException): message = f"File conversion failed after {len(attempts)} attempts:\n" for attempt in attempts: if attempt.exc_info is None: - message += " - {type(attempt.converter).__name__} provided no execution info." + message += f" - {type(attempt.converter).__name__} provided no execution info." else: message += f" - {type(attempt.converter).__name__} threw {attempt.exc_info[0].__name__} with message: {attempt.exc_info[1]}\n" From 5f75e16d201504c2efc6ec598d2fd2fed8b2ef6f Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 12 Mar 2025 11:08:06 -0700 Subject: [PATCH 53/75] Refactored tests. (#1120) * Refactored tests. * Fixed CI errors, and included misc tests. * Omit mskanji from streaminfo test. * Omit mskanji from no hints test. * Log results of debugging in comments (linked to Magika issue) * Added docs as to when to use misc tests. --- .../markitdown/src/markitdown/_markitdown.py | 31 +- packages/markitdown/tests/_test_vectors.py | 214 +++++++++++ packages/markitdown/tests/test_cli.py | 119 ------ packages/markitdown/tests/test_cli_misc.py | 35 ++ packages/markitdown/tests/test_cli_vectors.py | 170 +++++++++ ...test_markitdown.py => test_module_misc.py} | 355 +++--------------- .../markitdown/tests/test_module_vectors.py | 154 ++++++++ 7 files changed, 649 insertions(+), 429 deletions(-) create mode 100644 packages/markitdown/tests/_test_vectors.py delete mode 100644 packages/markitdown/tests/test_cli.py create mode 100644 packages/markitdown/tests/test_cli_misc.py create mode 100644 packages/markitdown/tests/test_cli_vectors.py rename packages/markitdown/tests/{test_markitdown.py => test_module_misc.py} (51%) create mode 100644 packages/markitdown/tests/test_module_vectors.py diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index b116927..2e9965a 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -244,7 +244,14 @@ class MarkItDown: or source.startswith("https://") or source.startswith("file://") ): - return self.convert_url(source, stream_info=stream_info, *kwargs) + # Rename the url argument to mock_url + # (Deprecated -- use stream_info) + _kwargs = {k: v for k, v in kwargs.items()} + if "url" in _kwargs: + _kwargs["mock_url"] = _kwargs["url"] + del _kwargs["url"] + + return self.convert_url(source, stream_info=stream_info, **_kwargs) else: return self.convert_local(source, stream_info=stream_info, **kwargs) # Path object @@ -350,12 +357,26 @@ class MarkItDown: return self._convert(file_stream=stream, stream_info_guesses=guesses, **kwargs) def convert_url( - self, url: str, **kwargs: Any + self, + url: str, + *, + stream_info: Optional[StreamInfo] = None, + file_extension: Optional[str] = None, # Deprecated -- use stream_info + mock_url: Optional[ + str + ] = None, # Mock the request as if it came from a different URL + **kwargs: Any, ) -> DocumentConverterResult: # TODO: fix kwargs type # Send a HTTP request to the URL response = self._requests_session.get(url, stream=True) response.raise_for_status() - return self.convert_response(response, **kwargs) + return self.convert_response( + response, + stream_info=stream_info, + file_extension=file_extension, + url=mock_url, + **kwargs, + ) def convert_response( self, @@ -660,10 +681,12 @@ class MarkItDown: return guesses - def _normalize_charset(self, charset: str) -> str: + def _normalize_charset(self, charset: str | None) -> str | None: """ Normalize a charset string to a canonical form. """ + if charset is None: + return None try: return codecs.lookup(charset).name except LookupError: diff --git a/packages/markitdown/tests/_test_vectors.py b/packages/markitdown/tests/_test_vectors.py new file mode 100644 index 0000000..5d2b2fc --- /dev/null +++ b/packages/markitdown/tests/_test_vectors.py @@ -0,0 +1,214 @@ +import dataclasses +from typing import List + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class FileTestVector(object): + filename: str + mimetype: str | None + charset: str | None + url: str | None + must_include: List[str] + must_not_include: List[str] + + +GENERAL_TEST_VECTORS = [ + FileTestVector( + filename="test.docx", + mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + charset=None, + url=None, + must_include=[ + "314b0a30-5b04-470b-b9f7-eed2c2bec74a", + "49e168b7-d2ae-407f-a055-2167576f39a1", + "## d666f1f7-46cb-42bd-9a39-9a39cf2a509f", + "# Abstract", + "# Introduction", + "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", + ], + must_not_include=[], + ), + FileTestVector( + filename="test.xlsx", + mimetype="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + charset=None, + url=None, + must_include=[ + "## 09060124-b5e7-4717-9d07-3c046eb", + "6ff4173b-42a5-4784-9b19-f49caff4d93d", + "affc7dad-52dc-4b98-9b5d-51e65d8a8ad0", + ], + must_not_include=[], + ), + FileTestVector( + filename="test.xls", + mimetype="application/vnd.ms-excel", + charset=None, + url=None, + must_include=[ + "## 09060124-b5e7-4717-9d07-3c046eb", + "6ff4173b-42a5-4784-9b19-f49caff4d93d", + "affc7dad-52dc-4b98-9b5d-51e65d8a8ad0", + ], + must_not_include=[], + ), + FileTestVector( + filename="test.pptx", + mimetype="application/vnd.openxmlformats-officedocument.presentationml.presentation", + charset=None, + url=None, + must_include=[ + "2cdda5c8-e50e-4db4-b5f0-9722a649f455", + "04191ea8-5c73-4215-a1d3-1cfb43aaaf12", + "44bf7d06-5e7a-4a40-a2e1-a2e42ef28c8a", + "1b92870d-e3b5-4e65-8153-919f4ff45592", + "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", + "a3f6004b-6f4f-4ea8-bee3-3741f4dc385f", # chart title + "2003", # chart value + ], + must_not_include=[], + ), + FileTestVector( + filename="test_outlook_msg.msg", + mimetype="application/vnd.ms-outlook", + charset=None, + url=None, + must_include=[ + "# Email Message", + "**From:** test.sender@example.com", + "**To:** test.recipient@example.com", + "**Subject:** Test Email Message", + "## Content", + "This is the body of the test email message", + ], + must_not_include=[], + ), + FileTestVector( + filename="test.pdf", + mimetype="application/pdf", + charset=None, + url=None, + must_include=[ + "While there is contemporaneous exploration of multi-agent approaches" + ], + must_not_include=[], + ), + FileTestVector( + filename="test_blog.html", + mimetype="text/html", + charset="utf-8", + url="https://microsoft.github.io/autogen/blog/2023/04/21/LLM-tuning-math", + must_include=[ + "Large language models (LLMs) are powerful tools that can generate natural language texts for various applications, such as chatbots, summarization, translation, and more. GPT-4 is currently the state of the art LLM in the world. Is model selection irrelevant? What about inference parameters?", + "an example where high cost can easily prevent a generic complex", + ], + must_not_include=[], + ), + FileTestVector( + filename="test_wikipedia.html", + mimetype="text/html", + charset="utf-8", + url="https://en.wikipedia.org/wiki/Microsoft", + must_include=[ + "Microsoft entered the operating system (OS) business in 1980 with its own version of [Unix]", + 'Microsoft was founded by [Bill Gates](/wiki/Bill_Gates "Bill Gates")', + ], + must_not_include=[ + "You are encouraged to create an account and log in", + "154 languages", + "move to sidebar", + ], + ), + FileTestVector( + filename="test_serp.html", + mimetype="text/html", + charset="utf-8", + url="https://www.bing.com/search?q=microsoft+wikipedia", + must_include=[ + "](https://en.wikipedia.org/wiki/Microsoft", + "Microsoft Corporation is **an American multinational corporation and technology company headquartered** in Redmond", + "1995–2007: Foray into the Web, Windows 95, Windows XP, and Xbox", + ], + must_not_include=[ + "https://www.bing.com/ck/a?!&&p=", + "data:image/svg+xml,%3Csvg%20width%3D", + ], + ), + FileTestVector( + filename="test_mskanji.csv", + mimetype="text/csv", + charset="cp932", + url=None, + must_include=[ + "名前,年齢,住所", + "佐藤太郎,30,東京", + "三木英子,25,大阪", + "髙橋淳,35,名古屋", + ], + must_not_include=[], + ), + FileTestVector( + filename="test.json", + mimetype="application/json", + charset="ascii", + url=None, + must_include=[ + "5b64c88c-b3c3-4510-bcb8-da0b200602d8", + "9700dc99-6685-40b4-9a3a-5e406dcb37f3", + ], + must_not_include=[], + ), + FileTestVector( + filename="test_rss.xml", + mimetype="text/xml", + charset="utf-8", + url=None, + must_include=[ + "# The Official Microsoft Blog", + "## Ignite 2024: Why nearly 70% of the Fortune 500 now use Microsoft 365 Copilot", + "In the case of AI, it is absolutely true that the industry is moving incredibly fast", + ], + must_not_include=[" None: - result = subprocess.run( - ["python", "-m", "markitdown", "--version"], capture_output=True, text=True - ) - - assert result.returncode == 0, f"CLI exited with error: {result.stderr}" - assert __version__ in result.stdout, f"Version not found in output: {result.stdout}" - - -def test_invalid_flag(shared_tmp_dir) -> None: - result = subprocess.run( - ["python", "-m", "markitdown", "--foobar"], capture_output=True, text=True - ) - - assert result.returncode != 0, f"CLI exited with error: {result.stderr}" - assert ( - "unrecognized arguments" in result.stderr - ), f"Expected 'unrecognized arguments' to appear in STDERR" - assert "SYNTAX" in result.stderr, f"Expected 'SYNTAX' to appear in STDERR" - - -def test_output_to_stdout(shared_tmp_dir) -> None: - # DOC X - result = subprocess.run( - ["python", "-m", "markitdown", os.path.join(TEST_FILES_DIR, "test.docx")], - capture_output=True, - text=True, - ) - - assert result.returncode == 0, f"CLI exited with error: {result.stderr}" - for test_string in DOCX_TEST_STRINGS: - assert ( - test_string in result.stdout - ), f"Expected string not found in output: {test_string}" - - -def test_output_to_file(shared_tmp_dir) -> None: - # DOC X, flag -o at the end - docx_output_file_1 = os.path.join(shared_tmp_dir, "test_docx_1.md") - result = subprocess.run( - [ - "python", - "-m", - "markitdown", - os.path.join(TEST_FILES_DIR, "test.docx"), - "-o", - docx_output_file_1, - ], - capture_output=True, - text=True, - ) - - assert result.returncode == 0, f"CLI exited with error: {result.stderr}" - assert os.path.exists( - docx_output_file_1 - ), f"Output file not created: {docx_output_file_1}" - - with open(docx_output_file_1, "r") as f: - output = f.read() - for test_string in DOCX_TEST_STRINGS: - assert ( - test_string in output - ), f"Expected string not found in output: {test_string}" - - # DOC X, flag -o at the beginning - docx_output_file_2 = os.path.join(shared_tmp_dir, "test_docx_2.md") - result = subprocess.run( - [ - "python", - "-m", - "markitdown", - "-o", - docx_output_file_2, - os.path.join(TEST_FILES_DIR, "test.docx"), - ], - capture_output=True, - text=True, - ) - - assert result.returncode == 0, f"CLI exited with error: {result.stderr}" - assert os.path.exists( - docx_output_file_2 - ), f"Output file not created: {docx_output_file_2}" - - with open(docx_output_file_2, "r") as f: - output = f.read() - for test_string in DOCX_TEST_STRINGS: - assert ( - test_string in output - ), f"Expected string not found in output: {test_string}" - - -if __name__ == "__main__": - """Runs this file's tests from the command line.""" - import tempfile - - with tempfile.TemporaryDirectory() as tmp_dir: - test_version(tmp_dir) - test_invalid_flag(tmp_dir) - test_output_to_stdout(tmp_dir) - test_output_to_file(tmp_dir) - print("All tests passed!") diff --git a/packages/markitdown/tests/test_cli_misc.py b/packages/markitdown/tests/test_cli_misc.py new file mode 100644 index 0000000..345d5cc --- /dev/null +++ b/packages/markitdown/tests/test_cli_misc.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 -m pytest +import subprocess +import pytest +from markitdown import __version__ + +# This file contains CLI tests that are not directly tested by the FileTestVectors. +# This includes things like help messages, version numbers, and invalid flags. + + +def test_version() -> None: + result = subprocess.run( + ["python", "-m", "markitdown", "--version"], capture_output=True, text=True + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert __version__ in result.stdout, f"Version not found in output: {result.stdout}" + + +def test_invalid_flag() -> None: + result = subprocess.run( + ["python", "-m", "markitdown", "--foobar"], capture_output=True, text=True + ) + + assert result.returncode != 0, f"CLI exited with error: {result.stderr}" + assert ( + "unrecognized arguments" in result.stderr + ), f"Expected 'unrecognized arguments' to appear in STDERR" + assert "SYNTAX" in result.stderr, f"Expected 'SYNTAX' to appear in STDERR" + + +if __name__ == "__main__": + """Runs this file's tests from the command line.""" + test_version() + test_invalid_flag() + print("All tests passed!") diff --git a/packages/markitdown/tests/test_cli_vectors.py b/packages/markitdown/tests/test_cli_vectors.py new file mode 100644 index 0000000..b2f068c --- /dev/null +++ b/packages/markitdown/tests/test_cli_vectors.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 -m pytest +import os +import time +import pytest +import subprocess +import locale +from typing import List + +if __name__ == "__main__": + from _test_vectors import GENERAL_TEST_VECTORS, FileTestVector +else: + from ._test_vectors import GENERAL_TEST_VECTORS, FileTestVector + +from markitdown import ( + MarkItDown, + UnsupportedFormatException, + FileConversionException, + StreamInfo, +) + +skip_remote = ( + True if os.environ.get("GITHUB_ACTIONS") else False +) # Don't run these tests in CI + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "test_files") +TEST_FILES_URL = "https://raw.githubusercontent.com/microsoft/markitdown/refs/heads/main/packages/markitdown/tests/test_files" + + +# Prepare CLI test vectors (remove vectors that require mockig the url) +CLI_TEST_VECTORS: List[FileTestVector] = [] +for test_vector in GENERAL_TEST_VECTORS: + if test_vector.url is not None: + continue + CLI_TEST_VECTORS.append(test_vector) + + +@pytest.fixture(scope="session") +def shared_tmp_dir(tmp_path_factory): + return tmp_path_factory.mktemp("pytest_tmp") + + +@pytest.mark.parametrize("test_vector", CLI_TEST_VECTORS) +def test_output_to_stdout(shared_tmp_dir, test_vector) -> None: + """Test that the CLI outputs to stdout correctly.""" + + result = subprocess.run( + [ + "python", + "-m", + "markitdown", + os.path.join(TEST_FILES_DIR, test_vector.filename), + ], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + for test_string in test_vector.must_include: + assert test_string in result.stdout + for test_string in test_vector.must_not_include: + assert test_string not in result.stdout + + +@pytest.mark.parametrize("test_vector", CLI_TEST_VECTORS) +def test_output_to_file(shared_tmp_dir, test_vector) -> None: + """Test that the CLI outputs to a file correctly.""" + + output_file = os.path.join(shared_tmp_dir, test_vector.filename + ".output") + result = subprocess.run( + [ + "python", + "-m", + "markitdown", + "-o", + output_file, + os.path.join(TEST_FILES_DIR, test_vector.filename), + ], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert os.path.exists(output_file), f"Output file not created: {output_file}" + + with open(output_file, "r") as f: + output_data = f.read() + for test_string in test_vector.must_include: + assert test_string in output_data + for test_string in test_vector.must_not_include: + assert test_string not in output_data + + os.remove(output_file) + assert not os.path.exists(output_file), f"Output file not deleted: {output_file}" + + +@pytest.mark.parametrize("test_vector", CLI_TEST_VECTORS) +def test_input_from_stdin_without_hints(shared_tmp_dir, test_vector) -> None: + """Test that the CLI readds from stdin correctly.""" + + test_input = b"" + with open(os.path.join(TEST_FILES_DIR, test_vector.filename), "rb") as stream: + test_input = stream.read() + + result = subprocess.run( + [ + "python", + "-m", + "markitdown", + os.path.join(TEST_FILES_DIR, test_vector.filename), + ], + input=test_input, + capture_output=True, + text=False, + ) + + stdout = result.stdout.decode(locale.getpreferredencoding()) + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + for test_string in test_vector.must_include: + assert test_string in stdout + for test_string in test_vector.must_not_include: + assert test_string not in stdout + + +@pytest.mark.skipif( + skip_remote, + reason="do not run tests that query external urls", +) +@pytest.mark.parametrize("test_vector", CLI_TEST_VECTORS) +def test_convert_url(shared_tmp_dir, test_vector): + """Test the conversion of a stream with no stream info.""" + # Note: tmp_dir is not used here, but is needed to match the signature + + markitdown = MarkItDown() + + time.sleep(1) # Ensure we don't hit rate limits + result = subprocess.run( + ["python", "-m", "markitdown", TEST_FILES_URL + "/" + test_vector.filename], + capture_output=True, + text=False, + ) + + stdout = result.stdout.decode(locale.getpreferredencoding()) + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + for test_string in test_vector.must_include: + assert test_string in stdout + for test_string in test_vector.must_not_include: + assert test_string not in stdout + + +if __name__ == "__main__": + import sys + import tempfile + + """Runs this file's tests from the command line.""" + + with tempfile.TemporaryDirectory() as tmp_dir: + for test_function in [ + test_output_to_stdout, + test_output_to_file, + test_input_from_stdin_without_hints, + test_convert_url, + ]: + for test_vector in CLI_TEST_VECTORS: + print( + f"Running {test_function.__name__} on {test_vector.filename}...", + end="", + ) + test_function(tmp_dir, test_vector) + print("OK") + print("All tests passed!") diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_module_misc.py similarity index 51% rename from packages/markitdown/tests/test_markitdown.py rename to packages/markitdown/tests/test_module_misc.py index f76ff8c..4079107 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_module_misc.py @@ -3,9 +3,7 @@ import io import os import shutil import openai - import pytest -import requests from markitdown import ( MarkItDown, @@ -14,6 +12,10 @@ from markitdown import ( StreamInfo, ) +# This file contains module tests that are not directly tested by the FileTestVectors. +# This includes things like helper functions and runtime conversion options +# (e.g., LLM clients, exiftool path, transcription services, etc.) + skip_remote = ( True if os.environ.get("GITHUB_ACTIONS") else False ) # Don't run these tests in CI @@ -59,36 +61,6 @@ YOUTUBE_TEST_STRINGS = [ "the model we're going to be using today is GPT 3.5 turbo", # From the transcript ] -XLSX_TEST_STRINGS = [ - "## 09060124-b5e7-4717-9d07-3c046eb", - "6ff4173b-42a5-4784-9b19-f49caff4d93d", - "affc7dad-52dc-4b98-9b5d-51e65d8a8ad0", -] - -XLS_TEST_STRINGS = [ - "## 09060124-b5e7-4717-9d07-3c046eb", - "6ff4173b-42a5-4784-9b19-f49caff4d93d", - "affc7dad-52dc-4b98-9b5d-51e65d8a8ad0", -] - -DOCX_TEST_STRINGS = [ - "314b0a30-5b04-470b-b9f7-eed2c2bec74a", - "49e168b7-d2ae-407f-a055-2167576f39a1", - "## d666f1f7-46cb-42bd-9a39-9a39cf2a509f", - "# Abstract", - "# Introduction", - "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", -] - -MSG_TEST_STRINGS = [ - "# Email Message", - "**From:** test.sender@example.com", - "**To:** test.recipient@example.com", - "**Subject:** Test Email Message", - "## Content", - "This is the body of the test email message", -] - DOCX_COMMENT_TEST_STRINGS = [ "314b0a30-5b04-470b-b9f7-eed2c2bec74a", "49e168b7-d2ae-407f-a055-2167576f39a1", @@ -100,6 +72,16 @@ DOCX_COMMENT_TEST_STRINGS = [ "Yet another comment in the doc. 55yiyi-asd09", ] +BLOG_TEST_URL = "https://microsoft.github.io/autogen/blog/2023/04/21/LLM-tuning-math" +BLOG_TEST_STRINGS = [ + "Large language models (LLMs) are powerful tools that can generate natural language texts for various applications, such as chatbots, summarization, translation, and more. GPT-4 is currently the state of the art LLM in the world. Is model selection irrelevant? What about inference parameters?", + "an example where high cost can easily prevent a generic complex", +] + +LLM_TEST_STRINGS = [ + "5bda1dd6", +] + PPTX_TEST_STRINGS = [ "2cdda5c8-e50e-4db4-b5f0-9722a649f455", "04191ea8-5c73-4215-a1d3-1cfb43aaaf12", @@ -110,57 +92,6 @@ PPTX_TEST_STRINGS = [ "2003", # chart value ] -BLOG_TEST_URL = "https://microsoft.github.io/autogen/blog/2023/04/21/LLM-tuning-math" -BLOG_TEST_STRINGS = [ - "Large language models (LLMs) are powerful tools that can generate natural language texts for various applications, such as chatbots, summarization, translation, and more. GPT-4 is currently the state of the art LLM in the world. Is model selection irrelevant? What about inference parameters?", - "an example where high cost can easily prevent a generic complex", -] - - -RSS_TEST_STRINGS = [ - "The Official Microsoft Blog", - "In the case of AI, it is absolutely true that the industry is moving incredibly fast", -] - - -WIKIPEDIA_TEST_URL = "https://en.wikipedia.org/wiki/Microsoft" -WIKIPEDIA_TEST_STRINGS = [ - "Microsoft entered the operating system (OS) business in 1980 with its own version of [Unix]", - 'Microsoft was founded by [Bill Gates](/wiki/Bill_Gates "Bill Gates")', -] -WIKIPEDIA_TEST_EXCLUDES = [ - "You are encouraged to create an account and log in", - "154 languages", - "move to sidebar", -] - -SERP_TEST_URL = "https://www.bing.com/search?q=microsoft+wikipedia" -SERP_TEST_STRINGS = [ - "](https://en.wikipedia.org/wiki/Microsoft", - "Microsoft Corporation is **an American multinational corporation and technology company headquartered** in Redmond", - "1995–2007: Foray into the Web, Windows 95, Windows XP, and Xbox", -] -SERP_TEST_EXCLUDES = [ - "https://www.bing.com/ck/a?!&&p=", - "data:image/svg+xml,%3Csvg%20width%3D", -] - -CSV_CP932_TEST_STRINGS = [ - "名前,年齢,住所", - "佐藤太郎,30,東京", - "三木英子,25,大阪", - "髙橋淳,35,名古屋", -] - -LLM_TEST_STRINGS = [ - "5bda1dd6", -] - -JSON_TEST_STRINGS = [ - "5b64c88c-b3c3-4510-bcb8-da0b200602d8", - "9700dc99-6685-40b4-9a3a-5e406dcb37f3", -] - # --- Helper Functions --- def validate_strings(result, expected_strings, exclude_strings=None): @@ -245,39 +176,29 @@ def test_stream_info_operations() -> None: assert updated_stream_info.url == "url.1" -def test_stream_info_guesses() -> None: - """Test StreamInfo guesses based on stream content.""" - - test_tuples = [ - ( - os.path.join(TEST_FILES_DIR, "test.xlsx"), - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ), - ( - os.path.join(TEST_FILES_DIR, "test.docx"), - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ), - ( - os.path.join(TEST_FILES_DIR, "test.pptx"), - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ), - (os.path.join(TEST_FILES_DIR, "test.xls"), "application/vnd.ms-excel"), - ] - +def test_docx_comments() -> None: markitdown = MarkItDown() - for file_path, expected_mimetype in test_tuples: - with open(file_path, "rb") as f: - guesses = markitdown._get_stream_info_guesses( - f, - StreamInfo( - filename=os.path.basename(file_path), - local_path=file_path, - extension=os.path.splitext(file_path)[1], - ), - ) - assert len(guesses) > 0 - assert guesses[0].mimetype == expected_mimetype - assert guesses[0].extension == os.path.splitext(file_path)[1] + + # Test DOCX processing, with comments and setting style_map on init + markitdown_with_style_map = MarkItDown(style_map="comment-reference => ") + result = markitdown_with_style_map.convert( + os.path.join(TEST_FILES_DIR, "test_with_comment.docx") + ) + validate_strings(result, DOCX_COMMENT_TEST_STRINGS) + + +def test_input_as_strings() -> None: + markitdown = MarkItDown() + + # Test input from a stream + input_data = b"

    Test

    " + result = markitdown.convert_stream(io.BytesIO(input_data)) + assert "# Test" in result.text_content + + # Test input with leading blank characters + input_data = b" \n\n\n

    Test

    " + result = markitdown.convert_stream(io.BytesIO(input_data)) + assert "# Test" in result.text_content @pytest.mark.skipif( @@ -292,194 +213,12 @@ def test_markitdown_remote() -> None: for test_string in PDF_TEST_STRINGS: assert test_string in result.text_content - # By stream - response = requests.get(PDF_TEST_URL) - result = markitdown.convert_stream( - io.BytesIO(response.content), file_extension=".pdf", url=PDF_TEST_URL - ) - for test_string in PDF_TEST_STRINGS: - assert test_string in result.text_content - # Youtube result = markitdown.convert(YOUTUBE_TEST_URL) for test_string in YOUTUBE_TEST_STRINGS: assert test_string in result.text_content -def test_markitdown_local() -> None: - markitdown = MarkItDown() - - # Test PDF processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.pdf")) - validate_strings(result, PDF_TEST_STRINGS) - - # Test XLSX processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.xlsx")) - validate_strings(result, XLSX_TEST_STRINGS) - - # Test XLS processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.xls")) - for test_string in XLS_TEST_STRINGS: - text_content = result.text_content.replace("\\", "") - assert test_string in text_content - - # Test DOCX processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.docx")) - validate_strings(result, DOCX_TEST_STRINGS) - - # Test DOCX processing, with comments - result = markitdown.convert( - os.path.join(TEST_FILES_DIR, "test_with_comment.docx"), - style_map="comment-reference => ", - ) - validate_strings(result, DOCX_COMMENT_TEST_STRINGS) - - # Test DOCX processing, with comments and setting style_map on init - markitdown_with_style_map = MarkItDown(style_map="comment-reference => ") - result = markitdown_with_style_map.convert( - os.path.join(TEST_FILES_DIR, "test_with_comment.docx") - ) - validate_strings(result, DOCX_COMMENT_TEST_STRINGS) - - # Test PPTX processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.pptx")) - validate_strings(result, PPTX_TEST_STRINGS) - - # Test HTML processing - result = markitdown.convert( - os.path.join(TEST_FILES_DIR, "test_blog.html"), url=BLOG_TEST_URL - ) - validate_strings(result, BLOG_TEST_STRINGS) - - # Test Wikipedia processing - result = markitdown.convert( - os.path.join(TEST_FILES_DIR, "test_wikipedia.html"), url=WIKIPEDIA_TEST_URL - ) - text_content = result.text_content.replace("\\", "") - validate_strings(result, WIKIPEDIA_TEST_STRINGS, WIKIPEDIA_TEST_EXCLUDES) - - # Test Bing processing - result = markitdown.convert( - os.path.join(TEST_FILES_DIR, "test_serp.html"), url=SERP_TEST_URL - ) - text_content = result.text_content.replace("\\", "") - validate_strings(result, SERP_TEST_STRINGS, SERP_TEST_EXCLUDES) - - # Test RSS processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_rss.xml")) - text_content = result.text_content.replace("\\", "") - for test_string in RSS_TEST_STRINGS: - assert test_string in text_content - - # Test MSG (Outlook email) processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg")) - validate_strings(result, MSG_TEST_STRINGS) - - # Test non-UTF-8 encoding - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_mskanji.csv")) - validate_strings(result, CSV_CP932_TEST_STRINGS) - - # Test JSON processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.json")) - validate_strings(result, JSON_TEST_STRINGS) - - # # Test ZIP file processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_files.zip")) - validate_strings(result, DOCX_TEST_STRINGS) - validate_strings(result, XLSX_TEST_STRINGS) - validate_strings(result, BLOG_TEST_STRINGS) - - # Test input from a stream - input_data = b"

    Test

    " - result = markitdown.convert_stream(io.BytesIO(input_data)) - assert "# Test" in result.text_content - - # Test input with leading blank characters - input_data = b" \n\n\n

    Test

    " - result = markitdown.convert_stream(io.BytesIO(input_data)) - assert "# Test" in result.text_content - - -def test_markitdown_streams() -> None: - markitdown = MarkItDown() - - # Test PDF processing - with open(os.path.join(TEST_FILES_DIR, "test.pdf"), "rb") as f: - result = markitdown.convert(f, file_extension=".pdf") - validate_strings(result, PDF_TEST_STRINGS) - - # Test XLSX processing - with open(os.path.join(TEST_FILES_DIR, "test.xlsx"), "rb") as f: - result = markitdown.convert(f, file_extension=".xlsx") - validate_strings(result, XLSX_TEST_STRINGS) - - # Test XLS processing - with open(os.path.join(TEST_FILES_DIR, "test.xls"), "rb") as f: - result = markitdown.convert(f, file_extension=".xls") - for test_string in XLS_TEST_STRINGS: - text_content = result.text_content.replace("\\", "") - assert test_string in text_content - - # Test DOCX processing - with open(os.path.join(TEST_FILES_DIR, "test.docx"), "rb") as f: - result = markitdown.convert(f, file_extension=".docx") - validate_strings(result, DOCX_TEST_STRINGS) - - # Test DOCX processing, with comments - with open(os.path.join(TEST_FILES_DIR, "test_with_comment.docx"), "rb") as f: - result = markitdown.convert( - f, - file_extension=".docx", - style_map="comment-reference => ", - ) - validate_strings(result, DOCX_COMMENT_TEST_STRINGS) - - # Test DOCX processing, with comments and setting style_map on init - markitdown_with_style_map = MarkItDown(style_map="comment-reference => ") - with open(os.path.join(TEST_FILES_DIR, "test_with_comment.docx"), "rb") as f: - result = markitdown_with_style_map.convert(f, file_extension=".docx") - validate_strings(result, DOCX_COMMENT_TEST_STRINGS) - - # Test PPTX processing - with open(os.path.join(TEST_FILES_DIR, "test.pptx"), "rb") as f: - result = markitdown.convert(f, file_extension=".pptx") - validate_strings(result, PPTX_TEST_STRINGS) - - # Test HTML processing - with open(os.path.join(TEST_FILES_DIR, "test_blog.html"), "rb") as f: - result = markitdown.convert(f, file_extension=".html", url=BLOG_TEST_URL) - validate_strings(result, BLOG_TEST_STRINGS) - - # Test Wikipedia processing - with open(os.path.join(TEST_FILES_DIR, "test_wikipedia.html"), "rb") as f: - result = markitdown.convert(f, file_extension=".html", url=WIKIPEDIA_TEST_URL) - text_content = result.text_content.replace("\\", "") - validate_strings(result, WIKIPEDIA_TEST_STRINGS, WIKIPEDIA_TEST_EXCLUDES) - - # Test Bing processing - with open(os.path.join(TEST_FILES_DIR, "test_serp.html"), "rb") as f: - result = markitdown.convert(f, file_extension=".html", url=SERP_TEST_URL) - text_content = result.text_content.replace("\\", "") - validate_strings(result, SERP_TEST_STRINGS, SERP_TEST_EXCLUDES) - - # Test RSS processing - with open(os.path.join(TEST_FILES_DIR, "test_rss.xml"), "rb") as f: - result = markitdown.convert(f, file_extension=".xml") - text_content = result.text_content.replace("\\", "") - for test_string in RSS_TEST_STRINGS: - assert test_string in text_content - - # Test MSG (Outlook email) processing - with open(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg"), "rb") as f: - result = markitdown.convert(f, file_extension=".msg") - validate_strings(result, MSG_TEST_STRINGS) - - # Test JSON processing - with open(os.path.join(TEST_FILES_DIR, "test.json"), "rb") as f: - result = markitdown.convert(f, file_extension=".json") - validate_strings(result, JSON_TEST_STRINGS) - - @pytest.mark.skipif( skip_remote, reason="do not run remotely run speech transcription tests", @@ -573,13 +312,17 @@ def test_markitdown_llm() -> None: if __name__ == "__main__": """Runs this file's tests from the command line.""" - test_stream_info_operations() - test_stream_info_guesses() - test_markitdown_remote() - test_markitdown_local() - test_markitdown_streams() - test_speech_transcription() - test_exceptions() - test_markitdown_exiftool() - test_markitdown_llm() + for test in [ + test_stream_info_operations, + test_docx_comments, + test_input_as_strings, + test_markitdown_remote, + test_speech_transcription, + test_exceptions, + test_markitdown_exiftool, + test_markitdown_llm, + ]: + print(f"Running {test.__name__}...", end="") + test() + print("OK") print("All tests passed!") diff --git a/packages/markitdown/tests/test_module_vectors.py b/packages/markitdown/tests/test_module_vectors.py new file mode 100644 index 0000000..873be75 --- /dev/null +++ b/packages/markitdown/tests/test_module_vectors.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 -m pytest +import os +import time +import pytest +import codecs + + +if __name__ == "__main__": + from _test_vectors import GENERAL_TEST_VECTORS +else: + from ._test_vectors import GENERAL_TEST_VECTORS + +from markitdown import ( + MarkItDown, + UnsupportedFormatException, + FileConversionException, + StreamInfo, +) + +skip_remote = ( + True if os.environ.get("GITHUB_ACTIONS") else False +) # Don't run these tests in CI + +TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "test_files") +TEST_FILES_URL = "https://raw.githubusercontent.com/microsoft/markitdown/refs/heads/main/packages/markitdown/tests/test_files" + + +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_guess_stream_info(test_vector): + """Test the ability to guess stream info.""" + markitdown = MarkItDown() + + local_path = os.path.join(TEST_FILES_DIR, test_vector.filename) + expected_extension = os.path.splitext(test_vector.filename)[1] + + with open(local_path, "rb") as stream: + guesses = markitdown._get_stream_info_guesses( + stream, + base_guess=StreamInfo( + filename=os.path.basename(test_vector.filename), + local_path=local_path, + extension=expected_extension, + ), + ) + + # For some limited exceptions, we can't guarantee the exact + # mimetype or extension, so we'll special-case them here. + if test_vector.filename in [ + "test_outlook_msg.msg", + "test_mskanji.csv", # See: https://github.com/google/magika/issues/983 + ]: + return + + assert guesses[0].mimetype == test_vector.mimetype + assert guesses[0].extension == expected_extension + assert guesses[0].charset == test_vector.charset + + +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_convert_local(test_vector): + """Test the conversion of a local file.""" + markitdown = MarkItDown() + + result = markitdown.convert( + os.path.join(TEST_FILES_DIR, test_vector.filename), url=test_vector.url + ) + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_convert_stream_with_hints(test_vector): + """Test the conversion of a stream with full stream info.""" + markitdown = MarkItDown() + + stream_info = StreamInfo( + extension=os.path.splitext(test_vector.filename)[1], + mimetype=test_vector.mimetype, + charset=test_vector.charset, + ) + + with open(os.path.join(TEST_FILES_DIR, test_vector.filename), "rb") as stream: + result = markitdown.convert( + stream, stream_info=stream_info, url=test_vector.url + ) + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_convert_stream_without_hints(test_vector): + """Test the conversion of a stream with no stream info.""" + markitdown = MarkItDown() + + # For some limited exceptions, we can't guarantee the exact + # mimetype or extension, so we'll special-case them here. + if test_vector.filename in [ + # This appears to be a subtle bug in magika. + # See: https://github.com/google/magika/issues/983 + "test_mskanji.csv", + ]: + return + + with open(os.path.join(TEST_FILES_DIR, test_vector.filename), "rb") as stream: + result = markitdown.convert(stream, url=test_vector.url) + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + +@pytest.mark.skipif( + skip_remote, + reason="do not run tests that query external urls", +) +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_convert_url(test_vector): + """Test the conversion of a stream with no stream info.""" + markitdown = MarkItDown() + + time.sleep(1) # Ensure we don't hit rate limits + + result = markitdown.convert( + TEST_FILES_URL + "/" + test_vector.filename, + url=test_vector.url, # Mock where this file would be found + ) + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + +if __name__ == "__main__": + import sys + + """Runs this file's tests from the command line.""" + for test_function in [ + test_guess_stream_info, + test_convert_local, + test_convert_stream_with_hints, + test_convert_stream_without_hints, + test_convert_url, + ]: + for test_vector in GENERAL_TEST_VECTORS: + print( + f"Running {test_function.__name__} on {test_vector.filename}...", end="" + ) + test_function(test_vector) + print("OK") + print("All tests passed!") From 12620f1545559faa13cf69527a9ef690936b330a Mon Sep 17 00:00:00 2001 From: Emanuele Meazzo <619619.psp@gmail.com> Date: Wed, 12 Mar 2025 19:26:23 +0100 Subject: [PATCH 54/75] Handle not supported plot type in pptx (#1122) * Handle not supported plot type in pptx * Fixed formatting. --- .../markitdown/converters/_pptx_converter.py | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index bcde6c9..e855382 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -211,24 +211,32 @@ class PptxConverter(DocumentConverter): return self._html_converter.convert_string(html_table).markdown.strip() + "\n" def _convert_chart_to_markdown(self, chart): - md = "\n\n### Chart" - if chart.has_title: - md += f": {chart.chart_title.text_frame.text}" - md += "\n\n" - data = [] - category_names = [c.label for c in chart.plots[0].categories] - series_names = [s.name for s in chart.series] - data.append(["Category"] + series_names) + try: + md = "\n\n### Chart" + if chart.has_title: + md += f": {chart.chart_title.text_frame.text}" + md += "\n\n" + data = [] + category_names = [c.label for c in chart.plots[0].categories] + series_names = [s.name for s in chart.series] + data.append(["Category"] + series_names) - for idx, category in enumerate(category_names): - row = [category] - for series in chart.series: - row.append(series.values[idx]) - data.append(row) + for idx, category in enumerate(category_names): + row = [category] + for series in chart.series: + row.append(series.values[idx]) + data.append(row) - markdown_table = [] - for row in data: - markdown_table.append("| " + " | ".join(map(str, row)) + " |") - header = markdown_table[0] - separator = "|" + "|".join(["---"] * len(data[0])) + "|" - return md + "\n".join([header, separator] + markdown_table[1:]) + markdown_table = [] + for row in data: + markdown_table.append("| " + " | ".join(map(str, row)) + " |") + header = markdown_table[0] + separator = "|" + "|".join(["---"] * len(data[0])) + "|" + return md + "\n".join([header, separator] + markdown_table[1:]) + except ValueError as e: + # Handle the specific error for unsupported chart types + if "unsupported plot type" in str(e): + return "\n\n[unsupported chart]\n\n" + except Exception: + # Catch any other exceptions that might occur + return "\n\n[unsupported chart]\n\n" From 0b815fb91649c7e43f429ef71069ac3085f07190 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 12 Mar 2025 11:44:19 -0700 Subject: [PATCH 55/75] Bumping version to 0.1.0a2 (#1123) --- packages/markitdown/src/markitdown/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index 91bf07b..5e345b6 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0a1" +__version__ = "0.1.0a2" From 6a9f09b153689b05f4419cfc9a6db2989db0ff26 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Wed, 12 Mar 2025 16:15:33 -0700 Subject: [PATCH 56/75] Updated Magika dependency. --- packages/markitdown/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index 1421852..0324ed4 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "beautifulsoup4", "requests", "markdownify", - "magika>=0.6.0rc1", + "magika>=0.6.1rc2", "charset-normalizer", ] From 09df7fe8df2e69c1a04261ec4d1e39d927819898 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 12 Mar 2025 19:18:11 -0700 Subject: [PATCH 57/75] Small fixes for autogen integration. (#1124) --- packages/markitdown/src/markitdown/__about__.py | 2 +- .../src/markitdown/converters/_transcribe_audio.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index 5e345b6..463e0c7 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0a2" +__version__ = "0.1.0a3" diff --git a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py index 3d02173..71c432b 100644 --- a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py +++ b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py @@ -7,7 +7,14 @@ from .._exceptions import MissingDependencyException # Save reporting of any exceptions for later _dependency_exc_info = None try: + # Suppress some deprecation warnings from the speech_recognition library + import warnings + + warnings.filterwarnings( + "ignore", category=DeprecationWarning, module="speech_recognition" + ) import speech_recognition as sr + import pydub except ImportError: # Preserve the error and stack trace for later From a78857bd438179592a8fe07f4ad9ea66b4346b54 Mon Sep 17 00:00:00 2001 From: afourney Date: Sat, 15 Mar 2025 18:34:51 -0700 Subject: [PATCH 58/75] Added epub test file. (#1130) --- packages/markitdown/tests/test_files/test.epub | Bin 0 -> 2677 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/markitdown/tests/test_files/test.epub diff --git a/packages/markitdown/tests/test_files/test.epub b/packages/markitdown/tests/test_files/test.epub new file mode 100644 index 0000000000000000000000000000000000000000..25c77b57ecd19d84e747738f63330238f68bc2bc GIT binary patch literal 2677 zcmZ{mc{tSDAIAq1MvWQE#WiRwS+bXuwVSQMjWJ`ySgr}fXpEsDaVHv5M3xZ6*h6wr zmaNIvwUi}f%NEI&HH7$$mg|?_{hjagd7ktA<8{t+-p~7d&KqgW0^|kkjx394D|cM3 z-0t6Qu`|kz;O0cRLUzKD$wYz!jzVxJDLRq8F39>4$jI*``&V+B8pD>$AT}Ar$Eb7q z=4a#$O$-zr+({H1f#l?=;NwQbywc*VRXTKFq$=xXWtwV1YW8VvZjFO%>^atAC=DYN zuvBUe8QZbyw_$TRxhdpiuuFpADa=_YBG}K^f#s55^Kpus8SFuZJPxX6s+`O$)6-#~ z?^~N4Z8jU_tGXN%(n@ty!0o(XmQfK1UXcLcdCIr79I;?jtd z!)w1cJw{0&I>IC78!A|C4LA%$YOR}CqZNGa$+HU(VpZ>ZcHB_6k|*Wn)`H4-VDOu& zwuYsmPZJC4Mk8G(rn@%4*7&~jc-wP^?kn>m+<7%X+lvcFxRNvJ)(@AZBSA zQY2~rFk1Uj-rFPLc6a0^a%0e{OEImA4dU2_4GdFxq+Y1S0l({I3p7r!c?A0r$Z3D7 z_?pFPmY`lOSq%rnscLx_GxOwUeF=&bF-dR;@;Jw~SfNeKizr$gt3|aN=D*GPqgxn+ z<~xBGJz=yBy}G6~I1m`Zo>(faI!PCN`6@}IUm@%-E-l|FfPCK=99N!4Zqw8v=?=7u zD$?y=&^>yvNUX$Y15P~XQh1ygCtA)N5_7GU2cI@StH>X@6|IodNjjar2Owupy?7b9@* zq|mlsn0_|QHE`C54if+%$uJGqKZbF^k^fLu@NuEI5#6E+Np)~&@S0{rx3Fe+eN%gH z?d^v}x>??TcXAE%!g0TmZ{N|T7QR*q$AUoH74ug=29P4EXe^iQ1a0dHkHfCb^sAsQ z>ucI?7KD^e(1x_0&8;4uSA-XwKVu3DLe1Sm!21&;J%sLfBniBUQfWEnnHc}!(8M5T zH&_NX@M_kz>`#ft4R%S%(%Yp=mU_uEas%TFxoNo0`Vn`)bjI9Rh+=PDzzXui$1eX(|P@2+asRVKxY1uAEXUYW#Faxza}`q^i(QKeF6H zw~l)CKCBDV%5NL7SQlIF9p{9w?}vV-|aByAm=Zb)`+MX9S_6{sLqgF%A?$ zO_4s$4H4?!yr8nNsaQxST=!6cFgazqie9$rMK2d{y!jk9tX1V!a(KS2y|emK)#)0m z%$Kw{L{=EM4hn8TBnaQSBY`{H26UFze|vWaVK#$zy-~y-!*(} zG}>&&UrRS=@tF);qzRj2Vp^l8WT(N8Hv#3fc_Jx7XS4imwOK~w`HB^73~B1EC*4v( zV7XHo2k94{2`FNy%~xQiHXQlQaAJ%^%y|2H;z|7U)&X!ZA4h?ujFqm}BAABF@*JEj zb`d;#<;hG0d@A>{yA`xHIb@9$@&f-&~rg{pYwV!aYO%U$?qx zZ;$Wmf#qYtY6=X6pD`3Z^uy8=cLxQMgHKZQb5fwvq2PIHs5HL`)V{w9bIoYRd*Fdc z`7$qezFr{k8ljJVmC4BZ92zsnH?ZTpQ&M}^)+$oCE4)Nu(WoIbcsbK z+~PKcu?brnmP`5q_}RI&u441t3|*NSx^n)|m4x&DW?O31bJT91U?>}FYyvjabU7aR zs&|zL`83jG$U=x0t&zwe4BN3AOF9Yu0Z$&%(;BgN95in`Xb!z7*BGS~V@Gc}BLh51!S7C@ zl+dQcI0=a5eUWQ&wx;#n<4vbAg#b8MF#%%Zay_>4)WT3LX=e#&Z3~t^A2&jN=b^bg zp56DUF1z{ox|*loiA1qnSs=|!LV#vseD^{*c7vj{UecEzZsRLil&C9}vCeH0*G{Q? zknL);9M^~wZD^Ef(LjI{+$-06w%jvkoik_KjDT&+T(k7U-aHm#cFNQV4Wuy>GcU{k z4=oG{zm5Qgrh5@)kNWq(@{Iui0+`ep(|?aI``6f?3ij6cm9h8!jIf{H|3vK3Ml3(* z|9UX?10mz*r3c QU}Job7@9oUO_hNE03xtGod5s; literal 0 HcmV?d00001 From 5c565b7d79a3ed8c4d8a83862583280e5e98cc1f Mon Sep 17 00:00:00 2001 From: afourney Date: Sat, 15 Mar 2025 23:12:48 -0700 Subject: [PATCH 59/75] Fix remaining mypy errors. (#1132) --- .../markitdown/src/markitdown/__main__.py | 2 +- .../converters/_bing_serp_converter.py | 6 +++++ .../converters/_outlook_msg_converter.py | 20 ++++++++++------ .../markitdown/converters/_rss_converter.py | 15 ++++++------ .../converters/_wikipedia_converter.py | 9 ++++--- .../converters/_youtube_converter.py | 24 +++++++++++++------ packages/markitdown/tests/test_cli_vectors.py | 4 +++- 7 files changed, 51 insertions(+), 29 deletions(-) diff --git a/packages/markitdown/src/markitdown/__main__.py b/packages/markitdown/src/markitdown/__main__.py index 8266f5c..6a5d01b 100644 --- a/packages/markitdown/src/markitdown/__main__.py +++ b/packages/markitdown/src/markitdown/__main__.py @@ -139,7 +139,7 @@ def main(): else: charset_hint = None - stream_info: str | None = None + stream_info = None if ( extension_hint is not None or mime_type_hint is not None diff --git a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py index 7dd9e24..3527d28 100644 --- a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py +++ b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py @@ -1,6 +1,7 @@ import io import re import base64 +import binascii from urllib.parse import parse_qs, urlparse from typing import Any, BinaryIO, Optional from bs4 import BeautifulSoup @@ -60,6 +61,8 @@ class BingSerpConverter(DocumentConverter): stream_info: StreamInfo, **kwargs: Any, # Options to pass to the converter ) -> DocumentConverterResult: + assert stream_info.url is not None + # Parse the query parameters parsed_params = parse_qs(urlparse(stream_info.url).query) query = parsed_params.get("q", [""])[0] @@ -79,6 +82,9 @@ class BingSerpConverter(DocumentConverter): _markdownify = _CustomMarkdownify() results = list() for result in soup.find_all(class_="b_algo"): + if not hasattr(result, "find_all"): + continue + # Rewrite redirect urls for a in result.find_all("a", href=True): parsed_href = urlparse(a["href"]) diff --git a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py index 8e20dc5..d216bea 100644 --- a/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py +++ b/packages/markitdown/src/markitdown/converters/_outlook_msg_converter.py @@ -9,7 +9,7 @@ from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE _dependency_exc_info = None olefile = None try: - import olefile + import olefile # type: ignore[no-redef] except ImportError: # Preserve the error and stack trace for later _dependency_exc_info = sys.exc_info() @@ -56,12 +56,13 @@ class OutlookMsgConverter(DocumentConverter): # Brue force, check if it's an Outlook file try: - msg = olefile.OleFileIO(file_stream) - toc = "\n".join([str(stream) for stream in msg.listdir()]) - return ( - "__properties_version1.0" in toc - and "__recip_version1.0_#00000000" in toc - ) + if olefile is not None: + msg = olefile.OleFileIO(file_stream) + toc = "\n".join([str(stream) for stream in msg.listdir()]) + return ( + "__properties_version1.0" in toc + and "__recip_version1.0_#00000000" in toc + ) except Exception as e: pass finally: @@ -89,7 +90,11 @@ class OutlookMsgConverter(DocumentConverter): _dependency_exc_info[2] ) + assert ( + olefile is not None + ) # If we made it this far, olefile should be available msg = olefile.OleFileIO(file_stream) + # Extract email metadata md_content = "# Email Message\n\n" @@ -121,6 +126,7 @@ class OutlookMsgConverter(DocumentConverter): def _get_stream_data(self, msg: Any, stream_path: str) -> Union[str, None]: """Helper to safely extract and decode stream data from the MSG file.""" + assert olefile is not None assert isinstance( msg, olefile.OleFileIO ) # Ensure msg is of the correct type (type hinting is not possible with the optional olefile package) diff --git a/packages/markitdown/src/markitdown/converters/_rss_converter.py b/packages/markitdown/src/markitdown/converters/_rss_converter.py index 31e5ad5..7c80d01 100644 --- a/packages/markitdown/src/markitdown/converters/_rss_converter.py +++ b/packages/markitdown/src/markitdown/converters/_rss_converter.py @@ -66,7 +66,7 @@ class RssConverter(DocumentConverter): file_stream.seek(cur_pos) return False - def _feed_type(self, doc: Any) -> str: + def _feed_type(self, doc: Any) -> str | None: if doc.getElementsByTagName("rss"): return "rss" elif doc.getElementsByTagName("feed"): @@ -130,10 +130,10 @@ class RssConverter(DocumentConverter): Returns None if the feed type is not recognized or something goes wrong. """ root = doc.getElementsByTagName("rss")[0] - channel = root.getElementsByTagName("channel") - if not channel: - return None - channel = channel[0] + channel_list = root.getElementsByTagName("channel") + if not channel_list: + raise ValueError("No channel found in RSS feed") + channel = channel_list[0] channel_title = self._get_data_by_tag_name(channel, "title") channel_description = self._get_data_by_tag_name(channel, "description") items = channel.getElementsByTagName("item") @@ -141,8 +141,6 @@ class RssConverter(DocumentConverter): md_text = f"# {channel_title}\n" if channel_description: md_text += f"{channel_description}\n" - if not items: - items = [] for item in items: title = self._get_data_by_tag_name(item, "title") description = self._get_data_by_tag_name(item, "description") @@ -183,5 +181,6 @@ class RssConverter(DocumentConverter): return None fc = nodes[0].firstChild if fc: - return fc.data + if hasattr(fc, "data"): + return fc.data return None diff --git a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py index 5b054af..39466c0 100644 --- a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py +++ b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py @@ -1,7 +1,7 @@ import io import re +import bs4 from typing import Any, BinaryIO, Optional -from bs4 import BeautifulSoup from .._base_converter import DocumentConverter, DocumentConverterResult from .._stream_info import StreamInfo @@ -57,7 +57,7 @@ class WikipediaConverter(DocumentConverter): ) -> DocumentConverterResult: # Parse the stream encoding = "utf-8" if stream_info.charset is None else stream_info.charset - soup = BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) + soup = bs4.BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) # Remove javascript and style blocks for script in soup(["script", "style"]): @@ -72,9 +72,8 @@ class WikipediaConverter(DocumentConverter): if body_elm: # What's the title - if title_elm and len(title_elm) > 0: - main_title = title_elm.string # type: ignore - assert isinstance(main_title, str) + if title_elm and isinstance(title_elm, bs4.Tag): + main_title = title_elm.string # Convert the page webpage_text = f"# {main_title}\n\n" + _CustomMarkdownify().convert_soup( diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py index 5a158d5..2ee5c69 100644 --- a/packages/markitdown/src/markitdown/converters/_youtube_converter.py +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -3,9 +3,9 @@ import json import time import io import re +import bs4 from typing import Any, BinaryIO, Optional, Dict, List, Union from urllib.parse import parse_qs, urlparse, unquote -from bs4 import BeautifulSoup from .._base_converter import DocumentConverter, DocumentConverterResult from .._stream_info import StreamInfo @@ -72,21 +72,31 @@ class YouTubeConverter(DocumentConverter): ) -> DocumentConverterResult: # Parse the stream encoding = "utf-8" if stream_info.charset is None else stream_info.charset - soup = BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) + soup = bs4.BeautifulSoup(file_stream, "html.parser", from_encoding=encoding) # Read the meta tags - metadata: Dict[str, str] = {"title": soup.title.string} + metadata: Dict[str, str] = {} + + if soup.title and soup.title.string: + metadata["title"] = soup.title.string + for meta in soup(["meta"]): + if not isinstance(meta, bs4.Tag): + continue + for a in meta.attrs: if a in ["itemprop", "property", "name"]: - content = meta.get("content", "") - if content: # Only add non-empty content - metadata[meta[a]] = content + key = str(meta.get(a, "")) + content = str(meta.get("content", "")) + if key and content: # Only add non-empty content + metadata[key] = content break # Try reading the description try: for script in soup(["script"]): + if not isinstance(script, bs4.Tag): + continue if not script.string: # Skip empty scripts continue content = script.string @@ -161,7 +171,7 @@ class YouTubeConverter(DocumentConverter): if transcript_text: webpage_text += f"\n### Transcript\n{transcript_text}\n" - title = title if title else soup.title.string + title = title if title else (soup.title.string if soup.title else "") assert isinstance(title, str) return DocumentConverterResult( diff --git a/packages/markitdown/tests/test_cli_vectors.py b/packages/markitdown/tests/test_cli_vectors.py index b2f068c..64128d6 100644 --- a/packages/markitdown/tests/test_cli_vectors.py +++ b/packages/markitdown/tests/test_cli_vectors.py @@ -114,7 +114,9 @@ def test_input_from_stdin_without_hints(shared_tmp_dir, test_vector) -> None: ) stdout = result.stdout.decode(locale.getpreferredencoding()) - assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert ( + result.returncode == 0 + ), f"CLI exited with error: {result.stderr.decode('utf-8')}" for test_string in test_vector.must_include: assert test_string in stdout for test_string in test_vector.must_not_include: From 53834fdd241f7981f7aa5cf1de0959e4d75346f8 Mon Sep 17 00:00:00 2001 From: afourney Date: Sat, 15 Mar 2025 23:41:35 -0700 Subject: [PATCH 60/75] Investigate and silence warnings. (#1133) --- .../src/markitdown/converters/_transcribe_audio.py | 5 +++++ .../src/markitdown/converters/_youtube_converter.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py index 71c432b..4a9a521 100644 --- a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py +++ b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py @@ -13,6 +13,11 @@ try: warnings.filterwarnings( "ignore", category=DeprecationWarning, module="speech_recognition" ) + warnings.filterwarnings( + "ignore", + category=SyntaxWarning, + module="pydub", # TODO: Migrate away from pydub + ) import speech_recognition as sr import pydub diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py index 2ee5c69..83ad57b 100644 --- a/packages/markitdown/src/markitdown/converters/_youtube_converter.py +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -4,6 +4,7 @@ import time import io import re import bs4 +import warnings from typing import Any, BinaryIO, Optional, Dict, List, Union from urllib.parse import parse_qs, urlparse, unquote @@ -13,6 +14,11 @@ from ._markdownify import _CustomMarkdownify # Optional YouTube transcription support try: + warnings.filterwarnings( + "ignore", + category=SyntaxWarning, + module="youtube_transcript_api", # Patch submitted to youtube-transcript-api + ) from youtube_transcript_api import YouTubeTranscriptApi IS_YOUTUBE_TRANSCRIPT_CAPABLE = True From c5f70b904ffc0f895fc33dbcb16cc9e04aaa3301 Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 17 Mar 2025 07:39:19 -0700 Subject: [PATCH 61/75] Have magika read from the stream. (#1136) --- packages/markitdown/pyproject.toml | 2 +- packages/markitdown/src/markitdown/__about__.py | 2 +- packages/markitdown/src/markitdown/_markitdown.py | 10 ++++++---- packages/markitdown/tests/test_module_vectors.py | 10 ---------- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index 0324ed4..bd38193 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "beautifulsoup4", "requests", "markdownify", - "magika>=0.6.1rc2", + "magika>=0.6.1rc3", "charset-normalizer", ] diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index 463e0c7..e54f3bc 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0a3" +__version__ = "0.1.0a4" diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 2e9965a..78319eb 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -610,14 +610,16 @@ class MarkItDown: # Call magika to guess from the stream cur_pos = file_stream.tell() try: - stream_bytes = file_stream.read() - - result = self._magika.identify_bytes(stream_bytes) + result = self._magika.identify_stream(file_stream) if result.status == "ok" and result.prediction.output.label != "unknown": # If it's text, also guess the charset charset = None if result.prediction.output.is_text: - charset_result = charset_normalizer.from_bytes(stream_bytes).best() + # Read the first 4k to guess the charset + file_stream.seek(cur_pos) + stream_page = file_stream.read(4096) + charset_result = charset_normalizer.from_bytes(stream_page).best() + if charset_result is not None: charset = self._normalize_charset(charset_result.encoding) diff --git a/packages/markitdown/tests/test_module_vectors.py b/packages/markitdown/tests/test_module_vectors.py index 873be75..9afffa5 100644 --- a/packages/markitdown/tests/test_module_vectors.py +++ b/packages/markitdown/tests/test_module_vectors.py @@ -47,7 +47,6 @@ def test_guess_stream_info(test_vector): # mimetype or extension, so we'll special-case them here. if test_vector.filename in [ "test_outlook_msg.msg", - "test_mskanji.csv", # See: https://github.com/google/magika/issues/983 ]: return @@ -96,15 +95,6 @@ def test_convert_stream_without_hints(test_vector): """Test the conversion of a stream with no stream info.""" markitdown = MarkItDown() - # For some limited exceptions, we can't guarantee the exact - # mimetype or extension, so we'll special-case them here. - if test_vector.filename in [ - # This appears to be a subtle bug in magika. - # See: https://github.com/google/magika/issues/983 - "test_mskanji.csv", - ]: - return - with open(os.path.join(TEST_FILES_DIR, test_vector.filename), "rb") as stream: result = markitdown.convert(stream, url=test_vector.url) for string in test_vector.must_include: From a93e0567e6f114fea6642e7603d9274fac9920d9 Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 17 Mar 2025 07:48:15 -0700 Subject: [PATCH 62/75] EPub Support. Adapted #123 to not use epublib. (#1131) * Adapted #123 to not use epublib. * Updated README.md --- README.md | 3 +- .../markitdown/src/markitdown/_markitdown.py | 2 + .../src/markitdown/converters/__init__.py | 2 + .../markitdown/converters/_epub_converter.py | 147 ++++++++++++++++++ packages/markitdown/tests/_test_vectors.py | 18 +++ 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 packages/markitdown/src/markitdown/converters/_epub_converter.py diff --git a/README.md b/README.md index 40f4b82..4401a0d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ MarkItDown is a lightweight Python utility for converting various files to Markd At present, MarkItDown supports: - PDF -- PowerPoint (reading in top-to-bottom, left-to-right order) +- PowerPoint - Word - Excel - Images (EXIF metadata and OCR) @@ -23,6 +23,7 @@ At present, MarkItDown supports: - Text-based formats (CSV, JSON, XML) - ZIP files (iterates over contents) - Youtube URLs +- EPubs - ... and more! ## Why Markdown? diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 78319eb..a8f7c9e 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -38,6 +38,7 @@ from .converters import ( AudioConverter, OutlookMsgConverter, ZipConverter, + EpubConverter, DocumentIntelligenceConverter, ) @@ -191,6 +192,7 @@ class MarkItDown: self.register_converter(IpynbConverter()) self.register_converter(PdfConverter()) self.register_converter(OutlookMsgConverter()) + self.register_converter(EpubConverter()) # Register Document Intelligence converter at the top of the stack if endpoint is provided docintel_endpoint = kwargs.get("docintel_endpoint") diff --git a/packages/markitdown/src/markitdown/converters/__init__.py b/packages/markitdown/src/markitdown/converters/__init__.py index f43efe3..09e3cb1 100644 --- a/packages/markitdown/src/markitdown/converters/__init__.py +++ b/packages/markitdown/src/markitdown/converters/__init__.py @@ -18,6 +18,7 @@ from ._audio_converter import AudioConverter from ._outlook_msg_converter import OutlookMsgConverter from ._zip_converter import ZipConverter from ._doc_intel_converter import DocumentIntelligenceConverter +from ._epub_converter import EpubConverter __all__ = [ "PlainTextConverter", @@ -37,4 +38,5 @@ __all__ = [ "OutlookMsgConverter", "ZipConverter", "DocumentIntelligenceConverter", + "EpubConverter", ] diff --git a/packages/markitdown/src/markitdown/converters/_epub_converter.py b/packages/markitdown/src/markitdown/converters/_epub_converter.py new file mode 100644 index 0000000..17d6d29 --- /dev/null +++ b/packages/markitdown/src/markitdown/converters/_epub_converter.py @@ -0,0 +1,147 @@ +import os +import zipfile +import xml.dom.minidom as minidom + +from typing import BinaryIO, Any, Dict, List + +from ._html_converter import HtmlConverter +from .._base_converter import DocumentConverter, DocumentConverterResult +from .._stream_info import StreamInfo + +ACCEPTED_MIME_TYPE_PREFIXES = [ + "application/epub", + "application/epub+zip", + "application/x-epub+zip", +] + +ACCEPTED_FILE_EXTENSIONS = [".epub"] + +MIME_TYPE_MAPPING = { + ".html": "text/html", + ".xhtml": "application/xhtml+xml", +} + + +class EpubConverter(HtmlConverter): + """ + Converts EPUB files to Markdown. Style information (e.g.m headings) and tables are preserved where possible. + """ + + def __init__(self): + super().__init__() + self._html_converter = HtmlConverter() + + def accepts( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> bool: + mimetype = (stream_info.mimetype or "").lower() + extension = (stream_info.extension or "").lower() + + if extension in ACCEPTED_FILE_EXTENSIONS: + return True + + for prefix in ACCEPTED_MIME_TYPE_PREFIXES: + if mimetype.startswith(prefix): + return True + + return False + + def convert( + self, + file_stream: BinaryIO, + stream_info: StreamInfo, + **kwargs: Any, # Options to pass to the converter + ) -> DocumentConverterResult: + with zipfile.ZipFile(file_stream, "r") as z: + # Extracts metadata (title, authors, language, publisher, date, description, cover) from an EPUB file.""" + + # Locate content.opf + container_dom = minidom.parse(z.open("META-INF/container.xml")) + opf_path = container_dom.getElementsByTagName("rootfile")[0].getAttribute( + "full-path" + ) + + # Parse content.opf + opf_dom = minidom.parse(z.open(opf_path)) + metadata: Dict[str, Any] = { + "title": self._get_text_from_node(opf_dom, "dc:title"), + "authors": self._get_all_texts_from_nodes(opf_dom, "dc:creator"), + "language": self._get_text_from_node(opf_dom, "dc:language"), + "publisher": self._get_text_from_node(opf_dom, "dc:publisher"), + "date": self._get_text_from_node(opf_dom, "dc:date"), + "description": self._get_text_from_node(opf_dom, "dc:description"), + "identifier": self._get_text_from_node(opf_dom, "dc:identifier"), + } + + # Extract manifest items (ID → href mapping) + manifest = { + item.getAttribute("id"): item.getAttribute("href") + for item in opf_dom.getElementsByTagName("item") + } + + # Extract spine order (ID refs) + spine_items = opf_dom.getElementsByTagName("itemref") + spine_order = [item.getAttribute("idref") for item in spine_items] + + # Convert spine order to actual file paths + base_path = "/".join( + opf_path.split("/")[:-1] + ) # Get base directory of content.opf + spine = [ + f"{base_path}/{manifest[item_id]}" if base_path else manifest[item_id] + for item_id in spine_order + if item_id in manifest + ] + + # Extract and convert the content + markdown_content: List[str] = [] + for file in spine: + if file in z.namelist(): + with z.open(file) as f: + filename = os.path.basename(file) + extension = os.path.splitext(filename)[1].lower() + mimetype = MIME_TYPE_MAPPING.get(extension) + converted_content = self._html_converter.convert( + f, + StreamInfo( + mimetype=mimetype, + extension=extension, + filename=filename, + ), + ) + markdown_content.append(converted_content.markdown.strip()) + + # Format and add the metadata + metadata_markdown = [] + for key, value in metadata.items(): + if isinstance(value, list): + value = ", ".join(value) + if value: + metadata_markdown.append(f"**{key.capitalize()}:** {value}") + + markdown_content.insert(0, "\n".join(metadata_markdown)) + + return DocumentConverterResult( + markdown="\n\n".join(markdown_content), title=metadata["title"] + ) + + def _get_text_from_node(self, dom: minidom.Document, tag_name: str) -> str | None: + """Convenience function to extract a single occurrence of a tag (e.g., title).""" + texts = self._get_all_texts_from_nodes(dom, tag_name) + if len(texts) > 0: + return texts[0] + else: + return None + + def _get_all_texts_from_nodes( + self, dom: minidom.Document, tag_name: str + ) -> List[str]: + """Helper function to extract all occurrences of a tag (e.g., multiple authors).""" + texts: List[str] = [] + for node in dom.getElementsByTagName(tag_name): + if node.firstChild and hasattr(node.firstChild, "nodeValue"): + texts.append(node.firstChild.nodeValue.strip()) + return texts diff --git a/packages/markitdown/tests/_test_vectors.py b/packages/markitdown/tests/_test_vectors.py index 5d2b2fc..8610108 100644 --- a/packages/markitdown/tests/_test_vectors.py +++ b/packages/markitdown/tests/_test_vectors.py @@ -211,4 +211,22 @@ GENERAL_TEST_VECTORS = [ ], must_not_include=[], ), + FileTestVector( + filename="test.epub", + mimetype="application/epub+zip", + charset=None, + url=None, + must_include=[ + "**Authors:** Test Author", + "A test EPUB document for MarkItDown testing", + "# Chapter 1: Test Content", + "This is a **test** paragraph with some formatting", + "* A bullet point", + "* Another point", + "# Chapter 2: More Content", + "*different* style", + "> This is a blockquote for testing", + ], + must_not_include=[], + ), ] From 716f74dcb92b622c023c78c9a50761bc63b0f598 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 19 Mar 2025 20:46:35 -0700 Subject: [PATCH 63/75] Consider anything with a charset as plain text-convertible. (#1142) --- .../converters/_plain_text_converter.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py index 4a21d3a..2e10405 100644 --- a/packages/markitdown/src/markitdown/converters/_plain_text_converter.py +++ b/packages/markitdown/src/markitdown/converters/_plain_text_converter.py @@ -17,12 +17,16 @@ except ImportError: ACCEPTED_MIME_TYPE_PREFIXES = [ "text/", "application/json", + "application/markdown", ] -# Mimetypes to ignore (commonly confused extensions) -IGNORE_MIME_TYPE_PREFIXES = [ - "text/vnd.in3d.spot", # .spo wich is confused with xls, doc, etc. - "text/vnd.graphviz", # .dot which is confused with xls, doc, etc. +ACCEPTED_FILE_EXTENSIONS = [ + ".txt", + ".text", + ".md", + ".markdown", + ".json", + ".jsonl", ] @@ -38,9 +42,14 @@ class PlainTextConverter(DocumentConverter): mimetype = (stream_info.mimetype or "").lower() extension = (stream_info.extension or "").lower() - for prefix in IGNORE_MIME_TYPE_PREFIXES: - if mimetype.startswith(prefix): - return False + # If we have a charset, we can safely assume it's text + # With Magika in the earlier stages, this handles most cases + if stream_info.charset is not None: + return True + + # Otherwise, check the mimetype and extension + if extension in ACCEPTED_FILE_EXTENSIONS: + return True for prefix in ACCEPTED_MIME_TYPE_PREFIXES: if mimetype.startswith(prefix): From cd6aa41361d47e3a1eaefc3a176e5d3ca7ab9994 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 19 Mar 2025 22:09:14 -0700 Subject: [PATCH 64/75] Adjust warning filters and update dependencies (#1143) Adjusts warning filters to be more contextual Updates dependencies for magika and youtube-transcript-api Updates the version to 0.1.0a5 in __about__.py --- packages/markitdown/pyproject.toml | 4 ++-- .../markitdown/src/markitdown/__about__.py | 2 +- .../converters/_transcribe_audio.py | 18 +++++---------- .../converters/_youtube_converter.py | 23 ++++++++----------- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index bd38193..9136108 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "beautifulsoup4", "requests", "markdownify", - "magika>=0.6.1rc3", + "magika~=0.6.1", "charset-normalizer", ] @@ -42,7 +42,7 @@ all = [ "olefile", "pydub", "SpeechRecognition", - "youtube-transcript-api", + "youtube-transcript-api~=1.0.0", "azure-ai-documentintelligence", "azure-identity" ] diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index e54f3bc..21790a2 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0a4" +__version__ = "0.1.0a5" diff --git a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py index 4a9a521..d558e46 100644 --- a/packages/markitdown/src/markitdown/converters/_transcribe_audio.py +++ b/packages/markitdown/src/markitdown/converters/_transcribe_audio.py @@ -7,20 +7,14 @@ from .._exceptions import MissingDependencyException # Save reporting of any exceptions for later _dependency_exc_info = None try: - # Suppress some deprecation warnings from the speech_recognition library + # Suppress some warnings on library import import warnings - warnings.filterwarnings( - "ignore", category=DeprecationWarning, module="speech_recognition" - ) - warnings.filterwarnings( - "ignore", - category=SyntaxWarning, - module="pydub", # TODO: Migrate away from pydub - ) - import speech_recognition as sr - - import pydub + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + warnings.filterwarnings("ignore", category=SyntaxWarning) + import speech_recognition as sr + import pydub except ImportError: # Preserve the error and stack trace for later _dependency_exc_info = sys.exc_info() diff --git a/packages/markitdown/src/markitdown/converters/_youtube_converter.py b/packages/markitdown/src/markitdown/converters/_youtube_converter.py index 83ad57b..b5a014c 100644 --- a/packages/markitdown/src/markitdown/converters/_youtube_converter.py +++ b/packages/markitdown/src/markitdown/converters/_youtube_converter.py @@ -4,22 +4,21 @@ import time import io import re import bs4 -import warnings from typing import Any, BinaryIO, Optional, Dict, List, Union from urllib.parse import parse_qs, urlparse, unquote from .._base_converter import DocumentConverter, DocumentConverterResult from .._stream_info import StreamInfo -from ._markdownify import _CustomMarkdownify # Optional YouTube transcription support try: - warnings.filterwarnings( - "ignore", - category=SyntaxWarning, - module="youtube_transcript_api", # Patch submitted to youtube-transcript-api - ) - from youtube_transcript_api import YouTubeTranscriptApi + # Suppress some warnings on library import + import warnings + + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=SyntaxWarning) + # Patch submitted upstream to fix the SyntaxWarning + from youtube_transcript_api import YouTubeTranscriptApi IS_YOUTUBE_TRANSCRIPT_CAPABLE = True except ModuleNotFoundError: @@ -148,6 +147,7 @@ class YouTubeConverter(DocumentConverter): webpage_text += f"\n### Description\n{description}\n" if IS_YOUTUBE_TRANSCRIPT_CAPABLE: + ytt_api = YouTubeTranscriptApi() transcript_text = "" parsed_url = urlparse(stream_info.url) # type: ignore params = parse_qs(parsed_url.query) # type: ignore @@ -159,7 +159,7 @@ class YouTubeConverter(DocumentConverter): ) # Retry the transcript fetching operation transcript = self._retry_operation( - lambda: YouTubeTranscriptApi.get_transcript( + lambda: ytt_api.fetch( video_id, languages=youtube_transcript_languages ), retries=3, # Retry 3 times @@ -167,11 +167,8 @@ class YouTubeConverter(DocumentConverter): ) if transcript: transcript_text = " ".join( - [part["text"] for part in transcript] + [part.text for part in transcript] ) # type: ignore - # Alternative formatting: - # formatter = TextFormatter() - # formatter.format_transcript(transcript) except Exception as e: print(f"Error fetching transcript: {e}") if transcript_text: From c0a511ecff2ce5ab96cd87887ea0863f2f3b6556 Mon Sep 17 00:00:00 2001 From: afourney Date: Thu, 20 Mar 2025 12:25:56 -0700 Subject: [PATCH 65/75] Updated docx file to include an image. (#1146) --- .../markitdown/tests/test_files/test.docx | Bin 17334 -> 135824 bytes 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packages/markitdown/tests/test_files/test.docx diff --git a/packages/markitdown/tests/test_files/test.docx b/packages/markitdown/tests/test_files/test.docx old mode 100644 new mode 100755 index 984018a6922f4d197bb7dd915cd422c5e990e509..79e281d71ad268cf8c5b5fe7c2bcac4d3b0c7b63 GIT binary patch literal 135824 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
    ISRWq4G%7>BOoPKWddR0ObY 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 RQeGhR2Bt!Neqa`0001VbSRDWW delta 12522 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 Date: Fri, 21 Mar 2025 09:50:23 +0800 Subject: [PATCH 66/75] Add support for preserving base64 encoded images (#1140) * optional reserve base64 string in markdown _CustomMarkdownify and pptx * add other converter para support * fix linter * Use *kwarg to pass keep_data_uri para. * Add module cli vector tests * Fixed formatting, and adjusted tests. --- .../markitdown/src/markitdown/__main__.py | 16 ++++- .../converters/_bing_serp_converter.py | 2 +- .../markitdown/converters/_docx_converter.py | 2 +- .../markitdown/converters/_html_converter.py | 4 +- .../src/markitdown/converters/_markdownify.py | 3 +- .../markitdown/converters/_pptx_converter.py | 22 +++++-- .../markitdown/converters/_rss_converter.py | 7 ++- .../converters/_wikipedia_converter.py | 8 +-- .../markitdown/converters/_xlsx_converter.py | 8 ++- packages/markitdown/tests/_test_vectors.py | 50 +++++++++++++++- packages/markitdown/tests/test_cli_vectors.py | 59 ++++++++++++++++++- .../markitdown/tests/test_module_vectors.py | 59 ++++++++++++++++++- 12 files changed, 214 insertions(+), 26 deletions(-) diff --git a/packages/markitdown/src/markitdown/__main__.py b/packages/markitdown/src/markitdown/__main__.py index 6a5d01b..3d8e396 100644 --- a/packages/markitdown/src/markitdown/__main__.py +++ b/packages/markitdown/src/markitdown/__main__.py @@ -104,6 +104,12 @@ def main(): help="List installed 3rd-party plugins. Plugins are loaded when using the -p or --use-plugin option.", ) + parser.add_argument( + "--keep-data-uris", + action="store_true", + help="Keep data URIs (like base64-encoded images) in the output. By default, data URIs are truncated.", + ) + parser.add_argument("filename", nargs="?") args = parser.parse_args() @@ -181,9 +187,15 @@ def main(): markitdown = MarkItDown(enable_plugins=args.use_plugins) if args.filename is None: - result = markitdown.convert_stream(sys.stdin.buffer, stream_info=stream_info) + result = markitdown.convert_stream( + sys.stdin.buffer, + stream_info=stream_info, + keep_data_uris=args.keep_data_uris, + ) else: - result = markitdown.convert(args.filename, stream_info=stream_info) + result = markitdown.convert( + args.filename, stream_info=stream_info, keep_data_uris=args.keep_data_uris + ) _handle_output(args, result) diff --git a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py index 3527d28..f65b85f 100644 --- a/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py +++ b/packages/markitdown/src/markitdown/converters/_bing_serp_converter.py @@ -79,7 +79,7 @@ class BingSerpConverter(DocumentConverter): slug.extract() # Parse the algorithmic results - _markdownify = _CustomMarkdownify() + _markdownify = _CustomMarkdownify(**kwargs) results = list() for result in soup.find_all(class_="b_algo"): if not hasattr(result, "find_all"): diff --git a/packages/markitdown/src/markitdown/converters/_docx_converter.py b/packages/markitdown/src/markitdown/converters/_docx_converter.py index c568acb..a9c469f 100644 --- a/packages/markitdown/src/markitdown/converters/_docx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_docx_converter.py @@ -73,5 +73,5 @@ class DocxConverter(HtmlConverter): style_map = kwargs.get("style_map", None) return self._html_converter.convert_string( - mammoth.convert_to_html(file_stream, style_map=style_map).value + mammoth.convert_to_html(file_stream, style_map=style_map).value, **kwargs ) diff --git a/packages/markitdown/src/markitdown/converters/_html_converter.py b/packages/markitdown/src/markitdown/converters/_html_converter.py index 8a8203d..dabb0d7 100644 --- a/packages/markitdown/src/markitdown/converters/_html_converter.py +++ b/packages/markitdown/src/markitdown/converters/_html_converter.py @@ -56,9 +56,9 @@ class HtmlConverter(DocumentConverter): body_elm = soup.find("body") webpage_text = "" if body_elm: - webpage_text = _CustomMarkdownify().convert_soup(body_elm) + webpage_text = _CustomMarkdownify(**kwargs).convert_soup(body_elm) else: - webpage_text = _CustomMarkdownify().convert_soup(soup) + webpage_text = _CustomMarkdownify(**kwargs).convert_soup(soup) assert isinstance(webpage_text, str) diff --git a/packages/markitdown/src/markitdown/converters/_markdownify.py b/packages/markitdown/src/markitdown/converters/_markdownify.py index ae99c0b..d98bdfb 100644 --- a/packages/markitdown/src/markitdown/converters/_markdownify.py +++ b/packages/markitdown/src/markitdown/converters/_markdownify.py @@ -17,6 +17,7 @@ class _CustomMarkdownify(markdownify.MarkdownConverter): def __init__(self, **options: Any): options["heading_style"] = options.get("heading_style", markdownify.ATX) + options["keep_data_uris"] = options.get("keep_data_uris", False) # Explicitly cast options to the expected type if necessary super().__init__(**options) @@ -101,7 +102,7 @@ class _CustomMarkdownify(markdownify.MarkdownConverter): return alt # Remove dataURIs - if src.startswith("data:"): + if src.startswith("data:") and not self.options["keep_data_uris"]: src = src.split(",")[0] + "..." return "![%s](%s%s)" % (alt, src, title_part) diff --git a/packages/markitdown/src/markitdown/converters/_pptx_converter.py b/packages/markitdown/src/markitdown/converters/_pptx_converter.py index e855382..087da32 100644 --- a/packages/markitdown/src/markitdown/converters/_pptx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_pptx_converter.py @@ -140,13 +140,20 @@ class PptxConverter(DocumentConverter): alt_text = re.sub(r"[\r\n\[\]]", " ", alt_text) alt_text = re.sub(r"\s+", " ", alt_text).strip() - # A placeholder name - filename = re.sub(r"\W", "", shape.name) + ".jpg" - md_content += "\n![" + alt_text + "](" + filename + ")\n" + # If keep_data_uris is True, use base64 encoding for images + if kwargs.get("keep_data_uris", False): + blob = shape.image.blob + content_type = shape.image.content_type or "image/png" + b64_string = base64.b64encode(blob).decode("utf-8") + md_content += f"\n![{alt_text}](data:{content_type};base64,{b64_string})\n" + else: + # A placeholder name + filename = re.sub(r"\W", "", shape.name) + ".jpg" + md_content += "\n![" + alt_text + "](" + filename + ")\n" # Tables if self._is_table(shape): - md_content += self._convert_table_to_markdown(shape.table) + md_content += self._convert_table_to_markdown(shape.table, **kwargs) # Charts if shape.has_chart: @@ -193,7 +200,7 @@ class PptxConverter(DocumentConverter): return True return False - def _convert_table_to_markdown(self, table): + def _convert_table_to_markdown(self, table, **kwargs): # Write the table as HTML, then convert it to Markdown html_table = "" first_row = True @@ -208,7 +215,10 @@ class PptxConverter(DocumentConverter): first_row = False html_table += "
    " - return self._html_converter.convert_string(html_table).markdown.strip() + "\n" + return ( + self._html_converter.convert_string(html_table, **kwargs).markdown.strip() + + "\n" + ) def _convert_chart_to_markdown(self, chart): try: diff --git a/packages/markitdown/src/markitdown/converters/_rss_converter.py b/packages/markitdown/src/markitdown/converters/_rss_converter.py index 7c80d01..6a0e4c1 100644 --- a/packages/markitdown/src/markitdown/converters/_rss_converter.py +++ b/packages/markitdown/src/markitdown/converters/_rss_converter.py @@ -28,6 +28,10 @@ CANDIDATE_FILE_EXTENSIONS = [ class RssConverter(DocumentConverter): """Convert RSS / Atom type to markdown""" + def __init__(self): + super().__init__() + self._kwargs = {} + def accepts( self, file_stream: BinaryIO, @@ -82,6 +86,7 @@ class RssConverter(DocumentConverter): stream_info: StreamInfo, **kwargs: Any, # Options to pass to the converter ) -> DocumentConverterResult: + self._kwargs = kwargs doc = minidom.parse(file_stream) feed_type = self._feed_type(doc) @@ -166,7 +171,7 @@ class RssConverter(DocumentConverter): try: # using bs4 because many RSS feeds have HTML-styled content soup = BeautifulSoup(content, "html.parser") - return _CustomMarkdownify().convert_soup(soup) + return _CustomMarkdownify(**self._kwargs).convert_soup(soup) except BaseException as _: return content diff --git a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py index 39466c0..c0f7e0e 100644 --- a/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py +++ b/packages/markitdown/src/markitdown/converters/_wikipedia_converter.py @@ -76,11 +76,11 @@ class WikipediaConverter(DocumentConverter): main_title = title_elm.string # Convert the page - webpage_text = f"# {main_title}\n\n" + _CustomMarkdownify().convert_soup( - body_elm - ) + webpage_text = f"# {main_title}\n\n" + _CustomMarkdownify( + **kwargs + ).convert_soup(body_elm) else: - webpage_text = _CustomMarkdownify().convert_soup(soup) + webpage_text = _CustomMarkdownify(**kwargs).convert_soup(soup) return DocumentConverterResult( markdown=webpage_text, diff --git a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py index 3d0e1ab..28f73a0 100644 --- a/packages/markitdown/src/markitdown/converters/_xlsx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_xlsx_converter.py @@ -86,7 +86,9 @@ class XlsxConverter(DocumentConverter): md_content += f"## {s}\n" html_content = sheets[s].to_html(index=False) md_content += ( - self._html_converter.convert_string(html_content).markdown.strip() + self._html_converter.convert_string( + html_content, **kwargs + ).markdown.strip() + "\n\n" ) @@ -146,7 +148,9 @@ class XlsConverter(DocumentConverter): md_content += f"## {s}\n" html_content = sheets[s].to_html(index=False) md_content += ( - self._html_converter.convert_string(html_content).markdown.strip() + self._html_converter.convert_string( + html_content, **kwargs + ).markdown.strip() + "\n\n" ) diff --git a/packages/markitdown/tests/_test_vectors.py b/packages/markitdown/tests/_test_vectors.py index 8610108..4a7b54a 100644 --- a/packages/markitdown/tests/_test_vectors.py +++ b/packages/markitdown/tests/_test_vectors.py @@ -25,8 +25,11 @@ GENERAL_TEST_VECTORS = [ "# Abstract", "# Introduction", "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", + "data:image/png;base64...", + ], + must_not_include=[ + "data:image/png;base64,iVBORw0KGgoAAAANSU", ], - must_not_include=[], ), FileTestVector( filename="test.xlsx", @@ -65,8 +68,9 @@ GENERAL_TEST_VECTORS = [ "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", "a3f6004b-6f4f-4ea8-bee3-3741f4dc385f", # chart title "2003", # chart value + "![This phrase of the caption is Human-written.](Picture4.jpg)", ], - must_not_include=[], + must_not_include=["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQE"], ), FileTestVector( filename="test_outlook_msg.msg", @@ -230,3 +234,45 @@ GENERAL_TEST_VECTORS = [ must_not_include=[], ), ] + + +DATA_URI_TEST_VECTORS = [ + FileTestVector( + filename="test.docx", + mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + charset=None, + url=None, + must_include=[ + "314b0a30-5b04-470b-b9f7-eed2c2bec74a", + "49e168b7-d2ae-407f-a055-2167576f39a1", + "## d666f1f7-46cb-42bd-9a39-9a39cf2a509f", + "# Abstract", + "# Introduction", + "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", + "data:image/png;base64,iVBORw0KGgoAAAANSU", + ], + must_not_include=[ + "data:image/png;base64...", + ], + ), + FileTestVector( + filename="test.pptx", + mimetype="application/vnd.openxmlformats-officedocument.presentationml.presentation", + charset=None, + url=None, + must_include=[ + "2cdda5c8-e50e-4db4-b5f0-9722a649f455", + "04191ea8-5c73-4215-a1d3-1cfb43aaaf12", + "44bf7d06-5e7a-4a40-a2e1-a2e42ef28c8a", + "1b92870d-e3b5-4e65-8153-919f4ff45592", + "AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation", + "a3f6004b-6f4f-4ea8-bee3-3741f4dc385f", # chart title + "2003", # chart value + "![This phrase of the caption is Human-written.]", # image caption + "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQE", + ], + must_not_include=[ + "![This phrase of the caption is Human-written.](Picture4.jpg)", + ], + ), +] diff --git a/packages/markitdown/tests/test_cli_vectors.py b/packages/markitdown/tests/test_cli_vectors.py index 64128d6..6030482 100644 --- a/packages/markitdown/tests/test_cli_vectors.py +++ b/packages/markitdown/tests/test_cli_vectors.py @@ -7,9 +7,17 @@ import locale from typing import List if __name__ == "__main__": - from _test_vectors import GENERAL_TEST_VECTORS, FileTestVector + from _test_vectors import ( + GENERAL_TEST_VECTORS, + DATA_URI_TEST_VECTORS, + FileTestVector, + ) else: - from ._test_vectors import GENERAL_TEST_VECTORS, FileTestVector + from ._test_vectors import ( + GENERAL_TEST_VECTORS, + DATA_URI_TEST_VECTORS, + FileTestVector, + ) from markitdown import ( MarkItDown, @@ -149,6 +157,39 @@ def test_convert_url(shared_tmp_dir, test_vector): assert test_string not in stdout +@pytest.mark.parametrize("test_vector", DATA_URI_TEST_VECTORS) +def test_output_to_file_with_data_uris(shared_tmp_dir, test_vector) -> None: + """Test CLI functionality when keep_data_uris is enabled""" + + output_file = os.path.join(shared_tmp_dir, test_vector.filename + ".output") + result = subprocess.run( + [ + "python", + "-m", + "markitdown", + "--keep-data-uris", + "-o", + output_file, + os.path.join(TEST_FILES_DIR, test_vector.filename), + ], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"CLI exited with error: {result.stderr}" + assert os.path.exists(output_file), f"Output file not created: {output_file}" + + with open(output_file, "r") as f: + output_data = f.read() + for test_string in test_vector.must_include: + assert test_string in output_data + for test_string in test_vector.must_not_include: + assert test_string not in output_data + + os.remove(output_file) + assert not os.path.exists(output_file), f"Output file not deleted: {output_file}" + + if __name__ == "__main__": import sys import tempfile @@ -156,6 +197,7 @@ if __name__ == "__main__": """Runs this file's tests from the command line.""" with tempfile.TemporaryDirectory() as tmp_dir: + # General tests for test_function in [ test_output_to_stdout, test_output_to_file, @@ -169,4 +211,17 @@ if __name__ == "__main__": ) test_function(tmp_dir, test_vector) print("OK") + + # Data URI tests + for test_function in [ + test_output_to_file_with_data_uris, + ]: + for test_vector in DATA_URI_TEST_VECTORS: + print( + f"Running {test_function.__name__} on {test_vector.filename}...", + end="", + ) + test_function(tmp_dir, test_vector) + print("OK") + print("All tests passed!") diff --git a/packages/markitdown/tests/test_module_vectors.py b/packages/markitdown/tests/test_module_vectors.py index 9afffa5..09e4a2b 100644 --- a/packages/markitdown/tests/test_module_vectors.py +++ b/packages/markitdown/tests/test_module_vectors.py @@ -6,9 +6,9 @@ import codecs if __name__ == "__main__": - from _test_vectors import GENERAL_TEST_VECTORS + from _test_vectors import GENERAL_TEST_VECTORS, DATA_URI_TEST_VECTORS else: - from ._test_vectors import GENERAL_TEST_VECTORS + from ._test_vectors import GENERAL_TEST_VECTORS, DATA_URI_TEST_VECTORS from markitdown import ( MarkItDown, @@ -124,10 +124,52 @@ def test_convert_url(test_vector): assert string not in result.markdown +@pytest.mark.parametrize("test_vector", DATA_URI_TEST_VECTORS) +def test_convert_with_data_uris(test_vector): + """Test API functionality when keep_data_uris is enabled""" + markitdown = MarkItDown() + + # Test local file conversion + result = markitdown.convert( + os.path.join(TEST_FILES_DIR, test_vector.filename), + keep_data_uris=True, + url=test_vector.url, + ) + + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + +@pytest.mark.parametrize("test_vector", DATA_URI_TEST_VECTORS) +def test_convert_stream_with_data_uris(test_vector): + """Test the conversion of a stream with no stream info.""" + markitdown = MarkItDown() + + stream_info = StreamInfo( + extension=os.path.splitext(test_vector.filename)[1], + mimetype=test_vector.mimetype, + charset=test_vector.charset, + ) + + with open(os.path.join(TEST_FILES_DIR, test_vector.filename), "rb") as stream: + result = markitdown.convert( + stream, stream_info=stream_info, keep_data_uris=True, url=test_vector.url + ) + + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + if __name__ == "__main__": import sys """Runs this file's tests from the command line.""" + + # General tests for test_function in [ test_guess_stream_info, test_convert_local, @@ -141,4 +183,17 @@ if __name__ == "__main__": ) test_function(test_vector) print("OK") + + # Data URI tests + for test_function in [ + test_convert_with_data_uris, + test_convert_stream_with_data_uris, + ]: + for test_vector in DATA_URI_TEST_VECTORS: + print( + f"Running {test_function.__name__} on {test_vector.filename}...", end="" + ) + test_function(test_vector) + print("OK") + print("All tests passed!") From efc55b260d65edf68016efb708d52ee0b2175d13 Mon Sep 17 00:00:00 2001 From: afourney Date: Fri, 21 Mar 2025 09:27:25 -0700 Subject: [PATCH 67/75] Bump version and resolve a console encoding error. (#1149) --- packages/markitdown/src/markitdown/__about__.py | 2 +- packages/markitdown/src/markitdown/__main__.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index 21790a2..129ad01 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0a5" +__version__ = "0.1.0a6" diff --git a/packages/markitdown/src/markitdown/__main__.py b/packages/markitdown/src/markitdown/__main__.py index 3d8e396..cfb1c6e 100644 --- a/packages/markitdown/src/markitdown/__main__.py +++ b/packages/markitdown/src/markitdown/__main__.py @@ -4,6 +4,7 @@ import argparse import sys import codecs +import locale from textwrap import dedent from importlib.metadata import entry_points from .__about__ import __version__ @@ -204,9 +205,14 @@ def _handle_output(args, result: DocumentConverterResult): """Handle output to stdout or file""" if args.output: with open(args.output, "w", encoding="utf-8") as f: - f.write(result.text_content) + f.write(result.markdown) else: - print(result.text_content) + # Handle stdout encoding errors more gracefully + print( + result.markdown.encode(sys.stdout.encoding, errors="replace").decode( + sys.stdout.encoding + ) + ) def _exit_with_error(message: str): From 2ffe6ea59140cf09afd1e7d41e1f29611bdba3e8 Mon Sep 17 00:00:00 2001 From: afourney Date: Sat, 22 Mar 2025 11:21:32 -0700 Subject: [PATCH 68/75] Bump version. (#1150) --- README.md | 5 +++-- packages/markitdown/src/markitdown/__about__.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4401a0d..c7dd5f3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ > [!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]~=0.1.0a1'` to have backward-compatible behavior. +> * 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. 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. @@ -37,7 +38,7 @@ are also highly token-efficient. ## Installation -To install MarkItDown, use pip: `pip install 'markitdown[all]~=0.1.0a1'`. Alternatively, you can install it from the source: +To install MarkItDown, use pip: `pip install 'markitdown[all]'`. Alternatively, you can install it from the source: ```bash git clone git@github.com:microsoft/markitdown.git diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index 129ad01..e8d7ba6 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0a6" +__version__ = "0.1.0" From e928b43afb0d57e1e71e98a9a2b4832aae46a0cc Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 24 Mar 2025 21:43:04 -0700 Subject: [PATCH 69/75] convert_url renamed to convert_uri, and now handles data and file URIs (#1153) --- .../markitdown/src/markitdown/_markitdown.py | 90 +++++++++++++++---- .../markitdown/src/markitdown/_uri_utils.py | 52 +++++++++++ packages/markitdown/tests/test_module_misc.py | 77 ++++++++++++++++ .../markitdown/tests/test_module_vectors.py | 54 +++++++++-- 4 files changed, 251 insertions(+), 22 deletions(-) create mode 100644 packages/markitdown/src/markitdown/_uri_utils.py diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index a8f7c9e..8f58db4 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -20,6 +20,7 @@ import charset_normalizer import codecs from ._stream_info import StreamInfo +from ._uri_utils import parse_data_uri, file_uri_to_path from .converters import ( PlainTextConverter, @@ -242,9 +243,10 @@ class MarkItDown: # Local path or url if isinstance(source, str): if ( - source.startswith("http://") - or source.startswith("https://") - or source.startswith("file://") + source.startswith("http:") + or source.startswith("https:") + or source.startswith("file:") + or source.startswith("data:") ): # Rename the url argument to mock_url # (Deprecated -- use stream_info) @@ -253,7 +255,7 @@ class MarkItDown: _kwargs["mock_url"] = _kwargs["url"] del _kwargs["url"] - return self.convert_url(source, stream_info=stream_info, **_kwargs) + return self.convert_uri(source, stream_info=stream_info, **_kwargs) else: return self.convert_local(source, stream_info=stream_info, **kwargs) # Path object @@ -363,22 +365,80 @@ class MarkItDown: url: str, *, stream_info: Optional[StreamInfo] = None, + file_extension: Optional[str] = None, + mock_url: Optional[str] = None, + **kwargs: Any, + ) -> DocumentConverterResult: + """Alias for convert_uri()""" + # convert_url will likely be deprecated in the future in favor of convert_uri + return self.convert_uri( + url, + stream_info=stream_info, + file_extension=file_extension, + mock_url=mock_url, + **kwargs, + ) + + def convert_uri( + self, + uri: str, + *, + stream_info: Optional[StreamInfo] = None, file_extension: Optional[str] = None, # Deprecated -- use stream_info mock_url: Optional[ str ] = None, # Mock the request as if it came from a different URL **kwargs: Any, - ) -> DocumentConverterResult: # TODO: fix kwargs type - # Send a HTTP request to the URL - response = self._requests_session.get(url, stream=True) - response.raise_for_status() - return self.convert_response( - response, - stream_info=stream_info, - file_extension=file_extension, - url=mock_url, - **kwargs, - ) + ) -> DocumentConverterResult: + uri = uri.strip() + + # File URIs + if uri.startswith("file:"): + netloc, path = file_uri_to_path(uri) + if netloc and netloc != "localhost": + raise ValueError( + f"Unsupported file URI: {uri}. Netloc must be empty or localhost." + ) + return self.convert_local( + path, + stream_info=stream_info, + file_extension=file_extension, + url=mock_url, + **kwargs, + ) + # Data URIs + elif uri.startswith("data:"): + mimetype, attributes, data = parse_data_uri(uri) + + base_guess = StreamInfo( + mimetype=mimetype, + charset=attributes.get("charset"), + ) + if stream_info is not None: + base_guess = base_guess.copy_and_update(stream_info) + + return self.convert_stream( + io.BytesIO(data), + stream_info=base_guess, + file_extension=file_extension, + url=mock_url, + **kwargs, + ) + # HTTP/HTTPS URIs + elif uri.startswith("http:") or uri.startswith("https:"): + response = self._requests_session.get(uri, stream=True) + response.raise_for_status() + return self.convert_response( + response, + stream_info=stream_info, + file_extension=file_extension, + url=mock_url, + **kwargs, + ) + else: + raise ValueError( + f"Unsupported URI scheme: {uri.split(':')[0]}. Supported schemes are: file:, data:, http:, https:" + ) def convert_response( self, diff --git a/packages/markitdown/src/markitdown/_uri_utils.py b/packages/markitdown/src/markitdown/_uri_utils.py new file mode 100644 index 0000000..603da63 --- /dev/null +++ b/packages/markitdown/src/markitdown/_uri_utils.py @@ -0,0 +1,52 @@ +import base64 +import os +from typing import Tuple, Dict +from urllib.request import url2pathname +from urllib.parse import urlparse, unquote_to_bytes + + +def file_uri_to_path(file_uri: str) -> Tuple[str | None, str]: + """Convert a file URI to a local file path""" + parsed = urlparse(file_uri) + if parsed.scheme != "file": + raise ValueError(f"Not a file URL: {file_uri}") + + netloc = parsed.netloc if parsed.netloc else None + path = os.path.abspath(url2pathname(parsed.path)) + return netloc, path + + +def parse_data_uri(uri: str) -> Tuple[str | None, Dict[str, str], bytes]: + if not uri.startswith("data:"): + raise ValueError("Not a data URI") + + header, _, data = uri.partition(",") + if not _: + raise ValueError("Malformed data URI, missing ',' separator") + + meta = header[5:] # Strip 'data:' + parts = meta.split(";") + + is_base64 = False + # Ends with base64? + if parts[-1] == "base64": + parts.pop() + is_base64 = True + + mime_type = None # Normally this would default to text/plain but we won't assume + if len(parts) and len(parts[0]) > 0: + # First part is the mime type + mime_type = parts.pop(0) + + attributes: Dict[str, str] = {} + for part in parts: + # Handle key=value pairs in the middle + if "=" in part: + key, value = part.split("=", 1) + attributes[key] = value + elif len(part) > 0: + attributes[part] = "" + + content = base64.b64decode(data) if is_base64 else unquote_to_bytes(data) + + return mime_type, attributes, content diff --git a/packages/markitdown/tests/test_module_misc.py b/packages/markitdown/tests/test_module_misc.py index 4079107..33e2c44 100644 --- a/packages/markitdown/tests/test_module_misc.py +++ b/packages/markitdown/tests/test_module_misc.py @@ -5,6 +5,8 @@ import shutil import openai import pytest +from markitdown._uri_utils import parse_data_uri, file_uri_to_path + from markitdown import ( MarkItDown, UnsupportedFormatException, @@ -176,6 +178,79 @@ def test_stream_info_operations() -> None: assert updated_stream_info.url == "url.1" +def test_data_uris() -> None: + # Test basic parsing of data URIs + data_uri = "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==" + mime_type, attributes, data = parse_data_uri(data_uri) + assert mime_type == "text/plain" + assert len(attributes) == 0 + assert data == b"Hello, World!" + + data_uri = "data:base64,SGVsbG8sIFdvcmxkIQ==" + mime_type, attributes, data = parse_data_uri(data_uri) + assert mime_type is None + assert len(attributes) == 0 + assert data == b"Hello, World!" + + data_uri = "data:text/plain;charset=utf-8;base64,SGVsbG8sIFdvcmxkIQ==" + mime_type, attributes, data = parse_data_uri(data_uri) + assert mime_type == "text/plain" + assert len(attributes) == 1 + assert attributes["charset"] == "utf-8" + assert data == b"Hello, World!" + + data_uri = "data:,Hello%2C%20World%21" + mime_type, attributes, data = parse_data_uri(data_uri) + assert mime_type is None + assert len(attributes) == 0 + assert data == b"Hello, World!" + + data_uri = "data:text/plain,Hello%2C%20World%21" + mime_type, attributes, data = parse_data_uri(data_uri) + assert mime_type == "text/plain" + assert len(attributes) == 0 + assert data == b"Hello, World!" + + data_uri = "data:text/plain;charset=utf-8,Hello%2C%20World%21" + mime_type, attributes, data = parse_data_uri(data_uri) + assert mime_type == "text/plain" + assert len(attributes) == 1 + assert attributes["charset"] == "utf-8" + assert data == b"Hello, World!" + + +def test_file_uris() -> None: + # Test file URI with an empty host + file_uri = "file:///path/to/file.txt" + netloc, path = file_uri_to_path(file_uri) + assert netloc is None + assert path == "/path/to/file.txt" + + # Test file URI with no host + file_uri = "file:/path/to/file.txt" + netloc, path = file_uri_to_path(file_uri) + assert netloc is None + assert path == "/path/to/file.txt" + + # Test file URI with localhost + file_uri = "file://localhost/path/to/file.txt" + netloc, path = file_uri_to_path(file_uri) + assert netloc == "localhost" + assert path == "/path/to/file.txt" + + # Test file URI with query parameters + file_uri = "file:///path/to/file.txt?param=value" + netloc, path = file_uri_to_path(file_uri) + assert netloc is None + assert path == "/path/to/file.txt" + + # Test file URI with fragment + file_uri = "file:///path/to/file.txt#fragment" + netloc, path = file_uri_to_path(file_uri) + assert netloc is None + assert path == "/path/to/file.txt" + + def test_docx_comments() -> None: markitdown = MarkItDown() @@ -314,6 +389,8 @@ if __name__ == "__main__": """Runs this file's tests from the command line.""" for test in [ test_stream_info_operations, + test_data_uris, + test_file_uris, test_docx_comments, test_input_as_strings, test_markitdown_remote, diff --git a/packages/markitdown/tests/test_module_vectors.py b/packages/markitdown/tests/test_module_vectors.py index 09e4a2b..98fd0c7 100644 --- a/packages/markitdown/tests/test_module_vectors.py +++ b/packages/markitdown/tests/test_module_vectors.py @@ -3,7 +3,9 @@ import os import time import pytest import codecs +import base64 +from pathlib import Path if __name__ == "__main__": from _test_vectors import GENERAL_TEST_VECTORS, DATA_URI_TEST_VECTORS @@ -108,8 +110,8 @@ def test_convert_stream_without_hints(test_vector): reason="do not run tests that query external urls", ) @pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) -def test_convert_url(test_vector): - """Test the conversion of a stream with no stream info.""" +def test_convert_http_uri(test_vector): + """Test the conversion of an HTTP:// or HTTPS:// URI.""" markitdown = MarkItDown() time.sleep(1) # Ensure we don't hit rate limits @@ -124,8 +126,44 @@ def test_convert_url(test_vector): assert string not in result.markdown +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_convert_file_uri(test_vector): + """Test the conversion of a file:// URI.""" + markitdown = MarkItDown() + + result = markitdown.convert( + Path(os.path.join(TEST_FILES_DIR, test_vector.filename)).as_uri(), + url=test_vector.url, + ) + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + +@pytest.mark.parametrize("test_vector", GENERAL_TEST_VECTORS) +def test_convert_data_uri(test_vector): + """Test the conversion of a data URI.""" + markitdown = MarkItDown() + + data = "" + with open(os.path.join(TEST_FILES_DIR, test_vector.filename), "rb") as stream: + data = base64.b64encode(stream.read()).decode("utf-8") + mimetype = test_vector.mimetype + data_uri = f"data:{mimetype};base64,{data}" + + result = markitdown.convert( + data_uri, + url=test_vector.url, + ) + for string in test_vector.must_include: + assert string in result.markdown + for string in test_vector.must_not_include: + assert string not in result.markdown + + @pytest.mark.parametrize("test_vector", DATA_URI_TEST_VECTORS) -def test_convert_with_data_uris(test_vector): +def test_convert_keep_data_uris(test_vector): """Test API functionality when keep_data_uris is enabled""" markitdown = MarkItDown() @@ -143,7 +181,7 @@ def test_convert_with_data_uris(test_vector): @pytest.mark.parametrize("test_vector", DATA_URI_TEST_VECTORS) -def test_convert_stream_with_data_uris(test_vector): +def test_convert_stream_keep_data_uris(test_vector): """Test the conversion of a stream with no stream info.""" markitdown = MarkItDown() @@ -175,7 +213,9 @@ if __name__ == "__main__": test_convert_local, test_convert_stream_with_hints, test_convert_stream_without_hints, - test_convert_url, + test_convert_http_uri, + test_convert_file_uri, + test_convert_data_uri, ]: for test_vector in GENERAL_TEST_VECTORS: print( @@ -186,8 +226,8 @@ if __name__ == "__main__": # Data URI tests for test_function in [ - test_convert_with_data_uris, - test_convert_stream_with_data_uris, + test_convert_keep_data_uris, + test_convert_stream_keep_data_uris, ]: for test_vector in DATA_URI_TEST_VECTORS: print( From c1f9a323ee43782ab014032ce8731f5de6eb87ed Mon Sep 17 00:00:00 2001 From: afourney Date: Mon, 24 Mar 2025 23:26:30 -0700 Subject: [PATCH 70/75] Bump version. (#1154) --- packages/markitdown/src/markitdown/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markitdown/src/markitdown/__about__.py b/packages/markitdown/src/markitdown/__about__.py index e8d7ba6..159c6da 100644 --- a/packages/markitdown/src/markitdown/__about__.py +++ b/packages/markitdown/src/markitdown/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2024-present Adam Fourney # # SPDX-License-Identifier: MIT -__version__ = "0.1.0" +__version__ = "0.1.1" From 3ca57986ef52b2928b30e08fbc827c5006760708 Mon Sep 17 00:00:00 2001 From: afourney Date: Tue, 25 Mar 2025 14:38:22 -0700 Subject: [PATCH 71/75] Basic SSE MCP Server for MarkItDown (#1155) * Added an initial minimal MCP server for MarkItDown * Added STDIO default option. * Added a Dockerfile, and updated the README accordingly. Also added instructions for Claude Desktop * Pin mcp version. --- packages/markitdown-mcp/Dockerfile | 26 ++++ packages/markitdown-mcp/README.md | 134 ++++++++++++++++++ packages/markitdown-mcp/pyproject.toml | 69 +++++++++ .../src/markitdown_mcp/__about__.py | 4 + .../src/markitdown_mcp/__init__.py | 9 ++ .../src/markitdown_mcp/__main__.py | 83 +++++++++++ .../src/markitdown_mcp/py.typed | 0 packages/markitdown-mcp/tests/__init__.py | 3 + 8 files changed, 328 insertions(+) create mode 100644 packages/markitdown-mcp/Dockerfile create mode 100644 packages/markitdown-mcp/README.md create mode 100644 packages/markitdown-mcp/pyproject.toml create mode 100644 packages/markitdown-mcp/src/markitdown_mcp/__about__.py create mode 100644 packages/markitdown-mcp/src/markitdown_mcp/__init__.py create mode 100644 packages/markitdown-mcp/src/markitdown_mcp/__main__.py create mode 100644 packages/markitdown-mcp/src/markitdown_mcp/py.typed create mode 100644 packages/markitdown-mcp/tests/__init__.py diff --git a/packages/markitdown-mcp/Dockerfile b/packages/markitdown-mcp/Dockerfile new file mode 100644 index 0000000..fe52a4b --- /dev/null +++ b/packages/markitdown-mcp/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.13-slim-bullseye + +ENV DEBIAN_FRONTEND=noninteractive +ENV EXIFTOOL_PATH=/usr/bin/exiftool +ENV FFMPEG_PATH=/usr/bin/ffmpeg + +# Runtime dependency +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + exiftool + +# Cleanup +RUN rm -rf /var/lib/apt/lists/* + +COPY . /app +RUN pip --no-cache-dir install /app + +WORKDIR /workdir + +# Default USERID and GROUPID +ARG USERID=nobody +ARG GROUPID=nogroup + +USER $USERID:$GROUPID + +ENTRYPOINT [ "markitdown-mcp" ] diff --git a/packages/markitdown-mcp/README.md b/packages/markitdown-mcp/README.md new file mode 100644 index 0000000..8fe18c7 --- /dev/null +++ b/packages/markitdown-mcp/README.md @@ -0,0 +1,134 @@ +# MarkItDown-MCP + +[![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) + +The `markitdown-mcp` package provides a lightweight STDIO and SSE MCP server for calling MarkItDown. + +It exposes one tool: `convert_to_markdown(uri)`, where uri can be any `http:`, `https:`, `file:`, or `data:` URI. + +## Installation + +To install the package, use pip: + +```bash +pip install markitdown-mcp +``` + +## Usage + +To run the MCP server, ussing STDIO (default) use the following command: + + +```bash +markitdown-mcp +``` + +To run the MCP server, ussing SSE use the following command: + +```bash +markitdown-mcp --sse --host 127.0.0.1 --port 3001 +``` + +## Running in Docker + +To run `markitdown-mcp` in Docker, build the Docker image using the provided Dockerfile: +```bash +docker build -t markitdown-mcp:latest . +``` + +And run it using: +```bash +docker run -it --rm markitdown-mcp:latest +``` +This will be sufficient for remote URIs. To access local files, you need to mount the local directory into the container. For example, if you want to access files in `/home/user/data`, you can run: + +```bash +docker run -it --rm -v /home/user/data:/workdir markitdown-mcp:latest +``` + +Once mounted, all files under data will be accessible under `/workdir` in the container. For example, if you have a file `example.txt` in `/home/user/data`, it will be accessible in the container at `/workdir/example.txt`. + +## Accessing from Claude Desktop + +It is recommended to use the Docker image when running the MCP server for Claude Desktop. + +Follow [these instrutions](https://modelcontextprotocol.io/quickstart/user#for-claude-desktop-users) to access Claude's `claude_desktop_config.json` file. + +Edit it to include the following JSON entry: + +```json +{ + "mcpServers": { + "markitdown": { + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "markitdown-mcp:latest" + ] + } + } +} +``` + +If you want to mount a directory, adjust it accordingly: + +```json +{ + "mcpServers": { + "markitdown": { + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "-v", + "/home/user/data:/workdir", + "markitdown-mcp:latest" + ] + } + } +} +``` + +## Debugging + +To debug the MCP server you can use the `mcpinspector` tool. + +```bash +npx @modelcontextprotocol/inspector +``` + +You can then connect to the insepctor through the specified host and port (e.g., `http://localhost:5173/`). + +If using STDIO: +* select `STDIO` as the transport type, +* input `markitdown-mcp` as the command, and +* click `Connect` + +If using SSE: +* select `SSE` as the transport type, +* input `http://127.0.0.1:3001/sse` as the URL, and +* click `Connect` + +Finally: +* click the `Tools` tab, +* click `List Tools`, +* click `convert_to_markdown`, and +* run the tool on any valid URI. + +## Security Considerations + +The server does not support authentication, and runs with the privileges if the user running it. For this reason, when running in SSE mode, it is recommended to run the server bound to `localhost` (default). + + +## Trademarks + +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. diff --git a/packages/markitdown-mcp/pyproject.toml b/packages/markitdown-mcp/pyproject.toml new file mode 100644 index 0000000..6cbc0e5 --- /dev/null +++ b/packages/markitdown-mcp/pyproject.toml @@ -0,0 +1,69 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "markitdown-mcp" +dynamic = ["version"] +description = 'An MCP server for the "markitdown" library.' +readme = "README.md" +requires-python = ">=3.10" +license = "MIT" +keywords = [] +authors = [ + { name = "Adam Fourney", email = "adamfo@microsoft.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dependencies = [ + "mcp~=1.5.0", + "markitdown[all]>=0.1.1,<0.2.0", +] + +[project.urls] +Documentation = "https://github.com/microsoft/markitdown#readme" +Issues = "https://github.com/microsoft/markitdown/issues" +Source = "https://github.com/microsoft/markitdown" + +[tool.hatch.version] +path = "src/markitdown_mcp/__about__.py" + +[project.scripts] +markitdown-mcp = "markitdown_mcp.__main__:main" + +[tool.hatch.envs.types] +extra-dependencies = [ + "mypy>=1.0.0", +] +[tool.hatch.envs.types.scripts] +check = "mypy --install-types --non-interactive {args:src/markitdown_mcp tests}" + +[tool.coverage.run] +source_pkgs = ["markitdown-mcp", "tests"] +branch = true +parallel = true +omit = [ + "src/markitdown_mcp/__about__.py", +] + +[tool.coverage.paths] +markitdown-mcp = ["src/markitdown_mcp", "*/markitdown-mcp/src/markitdown_mcp"] +tests = ["tests", "*/markitdown-mcp/tests"] + +[tool.coverage.report] +exclude_lines = [ + "no cov", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", +] + +[tool.hatch.build.targets.sdist] +only-include = ["src/markitdown_mcp"] diff --git a/packages/markitdown-mcp/src/markitdown_mcp/__about__.py b/packages/markitdown-mcp/src/markitdown_mcp/__about__.py new file mode 100644 index 0000000..a365900 --- /dev/null +++ b/packages/markitdown-mcp/src/markitdown_mcp/__about__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT +__version__ = "0.0.1a3" diff --git a/packages/markitdown-mcp/src/markitdown_mcp/__init__.py b/packages/markitdown-mcp/src/markitdown_mcp/__init__.py new file mode 100644 index 0000000..8464d59 --- /dev/null +++ b/packages/markitdown-mcp/src/markitdown_mcp/__init__.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT + +from .__about__ import __version__ + +__all__ = [ + "__version__", +] diff --git a/packages/markitdown-mcp/src/markitdown_mcp/__main__.py b/packages/markitdown-mcp/src/markitdown_mcp/__main__.py new file mode 100644 index 0000000..32b7527 --- /dev/null +++ b/packages/markitdown-mcp/src/markitdown_mcp/__main__.py @@ -0,0 +1,83 @@ +import sys +from typing import Any +from mcp.server.fastmcp import FastMCP +from starlette.applications import Starlette +from mcp.server.sse import SseServerTransport +from starlette.requests import Request +from starlette.routing import Mount, Route +from mcp.server import Server +from markitdown import MarkItDown +import uvicorn + +# Initialize FastMCP server for MarkItDown (SSE) +mcp = FastMCP("markitdown") + + +@mcp.tool() +async def convert_to_markdown(uri: str) -> str: + """Convert a resource described by an http:, https:, file: or data: URI to markdown""" + return MarkItDown().convert_uri(uri).markdown + + +def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: + sse = SseServerTransport("/messages/") + + async def handle_sse(request: Request) -> None: + async with sse.connect_sse( + request.scope, + request.receive, + request._send, + ) as (read_stream, write_stream): + await mcp_server.run( + read_stream, + write_stream, + mcp_server.create_initialization_options(), + ) + + return Starlette( + debug=debug, + routes=[ + Route("/sse", endpoint=handle_sse), + Mount("/messages/", app=sse.handle_post_message), + ], + ) + + +# Main entry point +def main(): + import argparse + + mcp_server = mcp._mcp_server + + parser = argparse.ArgumentParser(description="Run MCP SSE-based MarkItDown server") + + parser.add_argument( + "--sse", + action="store_true", + help="Run the server with SSE transport rather than STDIO (default: False)", + ) + parser.add_argument( + "--host", default=None, help="Host to bind to (default: 127.0.0.1)" + ) + parser.add_argument( + "--port", type=int, default=None, help="Port to listen on (default: 3001)" + ) + args = parser.parse_args() + + if not args.sse and (args.host or args.port): + parser.error("Host and port arguments are only valid when using SSE transport.") + sys.exit(1) + + if args.sse: + starlette_app = create_starlette_app(mcp_server, debug=True) + uvicorn.run( + starlette_app, + host=args.host if args.host else "127.0.0.1", + port=args.port if args.port else 3001, + ) + else: + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/packages/markitdown-mcp/src/markitdown_mcp/py.typed b/packages/markitdown-mcp/src/markitdown_mcp/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/packages/markitdown-mcp/tests/__init__.py b/packages/markitdown-mcp/tests/__init__.py new file mode 100644 index 0000000..44af7d7 --- /dev/null +++ b/packages/markitdown-mcp/tests/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2024-present Adam Fourney +# +# SPDX-License-Identifier: MIT From 73b9d573128c89a89c86aaa126835228c0c60efc Mon Sep 17 00:00:00 2001 From: afourney Date: Tue, 25 Mar 2025 14:52:24 -0700 Subject: [PATCH 72/75] Update badges (#1157) * Update badges in subpackages. --- packages/markitdown-mcp/README.md | 4 ++-- packages/markitdown-sample-plugin/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/markitdown-mcp/README.md b/packages/markitdown-mcp/README.md index 8fe18c7..923a42f 100644 --- a/packages/markitdown-mcp/README.md +++ b/packages/markitdown-mcp/README.md @@ -1,7 +1,7 @@ # MarkItDown-MCP -[![PyPI](https://img.shields.io/pypi/v/markitdown.svg)](https://pypi.org/project/markitdown/) -![PyPI - Downloads](https://img.shields.io/pypi/dd/markitdown) +[![PyPI](https://img.shields.io/pypi/v/markitdown-mcp.svg)](https://pypi.org/project/markitdown-mcp/) +![PyPI - Downloads](https://img.shields.io/pypi/dd/markitdown-mcp) [![Built by AutoGen Team](https://img.shields.io/badge/Built%20by-AutoGen%20Team-blue)](https://github.com/microsoft/autogen) The `markitdown-mcp` package provides a lightweight STDIO and SSE MCP server for calling MarkItDown. diff --git a/packages/markitdown-sample-plugin/README.md b/packages/markitdown-sample-plugin/README.md index fd7115f..adf1d9e 100644 --- a/packages/markitdown-sample-plugin/README.md +++ b/packages/markitdown-sample-plugin/README.md @@ -1,7 +1,7 @@ # MarkItDown Sample Plugin -[![PyPI](https://img.shields.io/pypi/v/markitdown.svg)](https://pypi.org/project/markitdown/) -![PyPI - Downloads](https://img.shields.io/pypi/dd/markitdown) +[![PyPI](https://img.shields.io/pypi/v/markitdown-sample-plugin.svg)](https://pypi.org/project/markitdown-sample-plugin/) +![PyPI - Downloads](https://img.shields.io/pypi/dd/markitdown-sample-plugin) [![Built by AutoGen Team](https://img.shields.io/badge/Built%20by-AutoGen%20Team-blue)](https://github.com/microsoft/autogen) From 9a951055f0f8de26ed0f3b914b50c39c28ca1f64 Mon Sep 17 00:00:00 2001 From: afourney Date: Tue, 25 Mar 2025 15:00:04 -0700 Subject: [PATCH 73/75] Update readme to point to the mcp package. (#1158) * Updated readme with link to the MCP package. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c7dd5f3..b807a6a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ ![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) +> [!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. + > [!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. From 9e067c42b647eaf14e842e70e47540b36c0c4a08 Mon Sep 17 00:00:00 2001 From: afourney Date: Wed, 26 Mar 2025 10:44:11 -0700 Subject: [PATCH 74/75] Make it easier to use AzureKeyCredentials with Azure Doc Intelligence (#1151) * Make it easier to use AzureKeyCredentials with Azure Doc Intelligence * Fixed mypy type error. * Added more fine-grained options over types. * Pass doc intel options further up the stack. --- .../markitdown/src/markitdown/_markitdown.py | 15 +- .../src/markitdown/converters/__init__.py | 6 +- .../converters/_doc_intel_converter.py | 143 +++++++++++++----- 3 files changed, 127 insertions(+), 37 deletions(-) diff --git a/packages/markitdown/src/markitdown/_markitdown.py b/packages/markitdown/src/markitdown/_markitdown.py index 8f58db4..54a0dc8 100644 --- a/packages/markitdown/src/markitdown/_markitdown.py +++ b/packages/markitdown/src/markitdown/_markitdown.py @@ -10,7 +10,7 @@ import traceback import io from dataclasses import dataclass from importlib.metadata import entry_points -from typing import Any, List, Optional, Union, BinaryIO +from typing import Any, List, Dict, Optional, Union, BinaryIO from pathlib import Path from urllib.parse import urlparse from warnings import warn @@ -198,8 +198,19 @@ class MarkItDown: # Register Document Intelligence converter at the top of the stack if endpoint is provided docintel_endpoint = kwargs.get("docintel_endpoint") if docintel_endpoint is not None: + docintel_args: Dict[str, Any] = {} + docintel_args["endpoint"] = docintel_endpoint + + docintel_credential = kwargs.get("docintel_credential") + if docintel_credential is not None: + docintel_args["credential"] = docintel_credential + + docintel_types = kwargs.get("docintel_file_types") + if docintel_types is not None: + docintel_args["file_types"] = docintel_types + self.register_converter( - DocumentIntelligenceConverter(endpoint=docintel_endpoint) + DocumentIntelligenceConverter(**docintel_args), ) self._builtins_enabled = True diff --git a/packages/markitdown/src/markitdown/converters/__init__.py b/packages/markitdown/src/markitdown/converters/__init__.py index 09e3cb1..c68d0c3 100644 --- a/packages/markitdown/src/markitdown/converters/__init__.py +++ b/packages/markitdown/src/markitdown/converters/__init__.py @@ -17,7 +17,10 @@ from ._image_converter import ImageConverter from ._audio_converter import AudioConverter from ._outlook_msg_converter import OutlookMsgConverter from ._zip_converter import ZipConverter -from ._doc_intel_converter import DocumentIntelligenceConverter +from ._doc_intel_converter import ( + DocumentIntelligenceConverter, + DocumentIntelligenceFileType, +) from ._epub_converter import EpubConverter __all__ = [ @@ -38,5 +41,6 @@ __all__ = [ "OutlookMsgConverter", "ZipConverter", "DocumentIntelligenceConverter", + "DocumentIntelligenceFileType", "EpubConverter", ] diff --git a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py index 2f116d0..5f4069b 100644 --- a/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py +++ b/packages/markitdown/src/markitdown/converters/_doc_intel_converter.py @@ -1,7 +1,9 @@ import sys import re +import os from typing import BinaryIO, Any, List +from enum import Enum from ._html_converter import HtmlConverter from .._base_converter import DocumentConverter, DocumentConverterResult @@ -18,6 +20,7 @@ try: AnalyzeResult, DocumentAnalysisFeature, ) + from azure.core.credentials import AzureKeyCredential, TokenCredential from azure.identity import DefaultAzureCredential except ImportError: # Preserve the error and stack trace for later @@ -29,38 +32,74 @@ except ImportError: CONTENT_FORMAT = "markdown" -OFFICE_MIME_TYPE_PREFIXES = [ - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.openxmlformats-officedocument.presentationml", - "application/xhtml", - "text/html", -] +class DocumentIntelligenceFileType(str, Enum): + """Enum of file types supported by the Document Intelligence Converter.""" -OTHER_MIME_TYPE_PREFIXES = [ - "application/pdf", - "application/x-pdf", - "text/html", - "image/", -] + # No OCR + DOCX = "docx" + PPTX = "pptx" + XLSX = "xlsx" + HTML = "html" + # OCR + PDF = "pdf" + JPEG = "jpeg" + PNG = "png" + BMP = "bmp" + TIFF = "tiff" -OFFICE_FILE_EXTENSIONS = [ - ".docx", - ".xlsx", - ".pptx", - ".html", - ".htm", -] -OTHER_FILE_EXTENSIONS = [ - ".pdf", - ".jpeg", - ".jpg", - ".png", - ".bmp", - ".tiff", - ".heif", -] +def _get_mime_type_prefixes(types: List[DocumentIntelligenceFileType]) -> List[str]: + """Get the MIME type prefixes for the given file types.""" + prefixes: List[str] = [] + for type_ in types: + if type_ == DocumentIntelligenceFileType.DOCX: + prefixes.append( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ) + elif type_ == DocumentIntelligenceFileType.PPTX: + prefixes.append( + "application/vnd.openxmlformats-officedocument.presentationml" + ) + elif type_ == DocumentIntelligenceFileType.XLSX: + prefixes.append( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ) + elif type_ == DocumentIntelligenceFileType.PDF: + prefixes.append("application/pdf") + prefixes.append("application/x-pdf") + elif type_ == DocumentIntelligenceFileType.JPEG: + prefixes.append("image/jpeg") + elif type_ == DocumentIntelligenceFileType.PNG: + prefixes.append("image/png") + elif type_ == DocumentIntelligenceFileType.BMP: + prefixes.append("image/bmp") + elif type_ == DocumentIntelligenceFileType.TIFF: + prefixes.append("image/tiff") + return prefixes + + +def _get_file_extensions(types: List[DocumentIntelligenceFileType]) -> List[str]: + """Get the file extensions for the given file types.""" + extensions: List[str] = [] + for type_ in types: + if type_ == DocumentIntelligenceFileType.DOCX: + extensions.append(".docx") + elif type_ == DocumentIntelligenceFileType.PPTX: + extensions.append(".pptx") + elif type_ == DocumentIntelligenceFileType.XLSX: + extensions.append(".xlsx") + elif type_ == DocumentIntelligenceFileType.PDF: + extensions.append(".pdf") + elif type_ == DocumentIntelligenceFileType.JPEG: + extensions.append(".jpg") + extensions.append(".jpeg") + elif type_ == DocumentIntelligenceFileType.PNG: + extensions.append(".png") + elif type_ == DocumentIntelligenceFileType.BMP: + extensions.append(".bmp") + elif type_ == DocumentIntelligenceFileType.TIFF: + extensions.append(".tiff") + return extensions class DocumentIntelligenceConverter(DocumentConverter): @@ -71,8 +110,30 @@ class DocumentIntelligenceConverter(DocumentConverter): *, endpoint: str, api_version: str = "2024-07-31-preview", + credential: AzureKeyCredential | TokenCredential | None = None, + file_types: List[DocumentIntelligenceFileType] = [ + DocumentIntelligenceFileType.DOCX, + DocumentIntelligenceFileType.PPTX, + DocumentIntelligenceFileType.XLSX, + DocumentIntelligenceFileType.PDF, + DocumentIntelligenceFileType.JPEG, + DocumentIntelligenceFileType.PNG, + DocumentIntelligenceFileType.BMP, + DocumentIntelligenceFileType.TIFF, + ], ): + """ + Initialize the DocumentIntelligenceConverter. + + Args: + endpoint (str): The endpoint for the Document Intelligence service. + api_version (str): The API version to use. Defaults to "2024-07-31-preview". + credential (AzureKeyCredential | TokenCredential | None): The credential to use for authentication. + file_types (List[DocumentIntelligenceFileType]): The file types to accept. Defaults to all supported file types. + """ + super().__init__() + self._file_types = file_types # Raise an error if the dependencies are not available. # This is different than other converters since this one isn't even instantiated @@ -86,12 +147,18 @@ class DocumentIntelligenceConverter(DocumentConverter): _dependency_exc_info[2] ) + if credential is None: + if os.environ.get("AZURE_API_KEY") is None: + credential = DefaultAzureCredential() + else: + credential = AzureKeyCredential(os.environ["AZURE_API_KEY"]) + self.endpoint = endpoint self.api_version = api_version self.doc_intel_client = DocumentIntelligenceClient( endpoint=self.endpoint, api_version=self.api_version, - credential=DefaultAzureCredential(), + credential=credential, ) def accepts( @@ -103,10 +170,10 @@ class DocumentIntelligenceConverter(DocumentConverter): mimetype = (stream_info.mimetype or "").lower() extension = (stream_info.extension or "").lower() - if extension in OFFICE_FILE_EXTENSIONS + OTHER_FILE_EXTENSIONS: + if extension in _get_file_extensions(self._file_types): return True - for prefix in OFFICE_MIME_TYPE_PREFIXES + OTHER_MIME_TYPE_PREFIXES: + for prefix in _get_mime_type_prefixes(self._file_types): if mimetype.startswith(prefix): return True @@ -121,10 +188,18 @@ class DocumentIntelligenceConverter(DocumentConverter): mimetype = (stream_info.mimetype or "").lower() extension = (stream_info.extension or "").lower() - if extension in OFFICE_FILE_EXTENSIONS: + # Types that don't support ocr + no_ocr_types = [ + DocumentIntelligenceFileType.DOCX, + DocumentIntelligenceFileType.PPTX, + DocumentIntelligenceFileType.XLSX, + DocumentIntelligenceFileType.HTML, + ] + + if extension in _get_file_extensions(no_ocr_types): return [] - for prefix in OFFICE_MIME_TYPE_PREFIXES: + for prefix in _get_mime_type_prefixes(no_ocr_types): if mimetype.startswith(prefix): return [] From 3fcd48cdfc651cbf508071c8d2fb7d82aeb075de Mon Sep 17 00:00:00 2001 From: Sathindu <11785398+sathinduga@users.noreply.github.com> Date: Fri, 28 Mar 2025 18:36:38 -0400 Subject: [PATCH 75/75] feat: render math equations in .docx documents (#1160) * feat: math equation rendering in .docx files * fix: import fix on .docx pre processing * test: add test cases for docx equation rendering * docs: add ThirdPartyNotices.md * refactor: reformatted with black --- packages/markitdown/ThirdPartyNotices.md | 232 ++++++++++ packages/markitdown/pyproject.toml | 3 +- .../markitdown/converter_utils/__init__.py | 0 .../converter_utils/docx/__init__.py | 0 .../converter_utils/docx/math/__init__.py | 0 .../converter_utils/docx/math/latex_dict.py | 273 ++++++++++++ .../converter_utils/docx/math/omml.py | 400 ++++++++++++++++++ .../converter_utils/docx/pre_process.py | 156 +++++++ .../markitdown/converters/_docx_converter.py | 5 +- .../tests/test_files/equations.docx | Bin 0 -> 15235 bytes packages/markitdown/tests/test_module_misc.py | 14 + 11 files changed, 1081 insertions(+), 2 deletions(-) create mode 100644 packages/markitdown/ThirdPartyNotices.md create mode 100644 packages/markitdown/src/markitdown/converter_utils/__init__.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/docx/__init__.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/docx/math/__init__.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/docx/math/latex_dict.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/docx/math/omml.py create mode 100644 packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py create mode 100644 packages/markitdown/tests/test_files/equations.docx diff --git a/packages/markitdown/ThirdPartyNotices.md b/packages/markitdown/ThirdPartyNotices.md new file mode 100644 index 0000000..44edd8f --- /dev/null +++ b/packages/markitdown/ThirdPartyNotices.md @@ -0,0 +1,232 @@ +# THIRD-PARTY SOFTWARE NOTICES AND INFORMATION + +**Do Not Translate or Localize** + +This project incorporates components from the projects listed below. The original copyright notices and the licenses +under which MarkItDown received such components are set forth below. MarkItDown reserves all rights not expressly +granted herein, whether by implication, estoppel or otherwise. + +1.dwml (https://github.com/xiilei/dwml) + +dwml NOTICES AND INFORMATION BEGIN HERE + +----------------------------------------- + +NOTE 1: What follows is a verbatim copy of dwml's LICENSE file, as it appeared on March 28th, 2025 - including +placeholders for the copyright owner and year. + +NOTE 2: The Apache License, Version 2.0, requires that modifications to the dwml source code be documented. +The following section summarizes these changes. The full details are available in the MarkItDown source code +repository under PR #1160 (https://github.com/microsoft/markitdown/pull/1160) + +This project incorporates `dwml/latex_dict.py` and `dwml/omml.py` files without any additional logic modifications (which +lives in `packages/markitdown/src/markitdown/converter_utils/docx/math` location). However, we have reformatted the code +according to `black` code formatter. From `tests/docx.py` file, we have used `DOCXML_ROOT` XML namespaces and the rest of +the file is not used. + +----------------------------------------- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +----------------------------------------- +END OF dwml NOTICES AND INFORMATION \ No newline at end of file diff --git a/packages/markitdown/pyproject.toml b/packages/markitdown/pyproject.toml index 9136108..79f67d2 100644 --- a/packages/markitdown/pyproject.toml +++ b/packages/markitdown/pyproject.toml @@ -38,6 +38,7 @@ all = [ "pandas", "openpyxl", "xlrd", + "lxml", "pdfminer.six", "olefile", "pydub", @@ -47,7 +48,7 @@ all = [ "azure-identity" ] pptx = ["python-pptx"] -docx = ["mammoth"] +docx = ["mammoth", "lxml"] xlsx = ["pandas", "openpyxl"] xls = ["pandas", "xlrd"] pdf = ["pdfminer.six"] diff --git a/packages/markitdown/src/markitdown/converter_utils/__init__.py b/packages/markitdown/src/markitdown/converter_utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/markitdown/src/markitdown/converter_utils/docx/__init__.py b/packages/markitdown/src/markitdown/converter_utils/docx/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/markitdown/src/markitdown/converter_utils/docx/math/__init__.py b/packages/markitdown/src/markitdown/converter_utils/docx/math/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/markitdown/src/markitdown/converter_utils/docx/math/latex_dict.py b/packages/markitdown/src/markitdown/converter_utils/docx/math/latex_dict.py new file mode 100644 index 0000000..9b47382 --- /dev/null +++ b/packages/markitdown/src/markitdown/converter_utils/docx/math/latex_dict.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- + +""" +Adapted from https://github.com/xiilei/dwml/blob/master/dwml/latex_dict.py +On 25/03/2025 +""" + +from __future__ import unicode_literals + +CHARS = ("{", "}", "_", "^", "#", "&", "$", "%", "~") + +BLANK = "" +BACKSLASH = "\\" +ALN = "&" + +CHR = { + # Unicode : Latex Math Symbols + # Top accents + "\u0300": "\\grave{{{0}}}", + "\u0301": "\\acute{{{0}}}", + "\u0302": "\\hat{{{0}}}", + "\u0303": "\\tilde{{{0}}}", + "\u0304": "\\bar{{{0}}}", + "\u0305": "\\overbar{{{0}}}", + "\u0306": "\\breve{{{0}}}", + "\u0307": "\\dot{{{0}}}", + "\u0308": "\\ddot{{{0}}}", + "\u0309": "\\ovhook{{{0}}}", + "\u030a": "\\ocirc{{{0}}}}", + "\u030c": "\\check{{{0}}}}", + "\u0310": "\\candra{{{0}}}", + "\u0312": "\\oturnedcomma{{{0}}}", + "\u0315": "\\ocommatopright{{{0}}}", + "\u031a": "\\droang{{{0}}}", + "\u0338": "\\not{{{0}}}", + "\u20d0": "\\leftharpoonaccent{{{0}}}", + "\u20d1": "\\rightharpoonaccent{{{0}}}", + "\u20d2": "\\vertoverlay{{{0}}}", + "\u20d6": "\\overleftarrow{{{0}}}", + "\u20d7": "\\vec{{{0}}}", + "\u20db": "\\dddot{{{0}}}", + "\u20dc": "\\ddddot{{{0}}}", + "\u20e1": "\\overleftrightarrow{{{0}}}", + "\u20e7": "\\annuity{{{0}}}", + "\u20e9": "\\widebridgeabove{{{0}}}", + "\u20f0": "\\asteraccent{{{0}}}", + # Bottom accents + "\u0330": "\\wideutilde{{{0}}}", + "\u0331": "\\underbar{{{0}}}", + "\u20e8": "\\threeunderdot{{{0}}}", + "\u20ec": "\\underrightharpoondown{{{0}}}", + "\u20ed": "\\underleftharpoondown{{{0}}}", + "\u20ee": "\\underledtarrow{{{0}}}", + "\u20ef": "\\underrightarrow{{{0}}}", + # Over | group + "\u23b4": "\\overbracket{{{0}}}", + "\u23dc": "\\overparen{{{0}}}", + "\u23de": "\\overbrace{{{0}}}", + # Under| group + "\u23b5": "\\underbracket{{{0}}}", + "\u23dd": "\\underparen{{{0}}}", + "\u23df": "\\underbrace{{{0}}}", +} + +CHR_BO = { + # Big operators, + "\u2140": "\\Bbbsum", + "\u220f": "\\prod", + "\u2210": "\\coprod", + "\u2211": "\\sum", + "\u222b": "\\int", + "\u22c0": "\\bigwedge", + "\u22c1": "\\bigvee", + "\u22c2": "\\bigcap", + "\u22c3": "\\bigcup", + "\u2a00": "\\bigodot", + "\u2a01": "\\bigoplus", + "\u2a02": "\\bigotimes", +} + +T = { + "\u2192": "\\rightarrow ", + # Greek letters + "\U0001d6fc": "\\alpha ", + "\U0001d6fd": "\\beta ", + "\U0001d6fe": "\\gamma ", + "\U0001d6ff": "\\theta ", + "\U0001d700": "\\epsilon ", + "\U0001d701": "\\zeta ", + "\U0001d702": "\\eta ", + "\U0001d703": "\\theta ", + "\U0001d704": "\\iota ", + "\U0001d705": "\\kappa ", + "\U0001d706": "\\lambda ", + "\U0001d707": "\\m ", + "\U0001d708": "\\n ", + "\U0001d709": "\\xi ", + "\U0001d70a": "\\omicron ", + "\U0001d70b": "\\pi ", + "\U0001d70c": "\\rho ", + "\U0001d70d": "\\varsigma ", + "\U0001d70e": "\\sigma ", + "\U0001d70f": "\\ta ", + "\U0001d710": "\\upsilon ", + "\U0001d711": "\\phi ", + "\U0001d712": "\\chi ", + "\U0001d713": "\\psi ", + "\U0001d714": "\\omega ", + "\U0001d715": "\\partial ", + "\U0001d716": "\\varepsilon ", + "\U0001d717": "\\vartheta ", + "\U0001d718": "\\varkappa ", + "\U0001d719": "\\varphi ", + "\U0001d71a": "\\varrho ", + "\U0001d71b": "\\varpi ", + # Relation symbols + "\u2190": "\\leftarrow ", + "\u2191": "\\uparrow ", + "\u2192": "\\rightarrow ", + "\u2193": "\\downright ", + "\u2194": "\\leftrightarrow ", + "\u2195": "\\updownarrow ", + "\u2196": "\\nwarrow ", + "\u2197": "\\nearrow ", + "\u2198": "\\searrow ", + "\u2199": "\\swarrow ", + "\u22ee": "\\vdots ", + "\u22ef": "\\cdots ", + "\u22f0": "\\adots ", + "\u22f1": "\\ddots ", + "\u2260": "\\ne ", + "\u2264": "\\leq ", + "\u2265": "\\geq ", + "\u2266": "\\leqq ", + "\u2267": "\\geqq ", + "\u2268": "\\lneqq ", + "\u2269": "\\gneqq ", + "\u226a": "\\ll ", + "\u226b": "\\gg ", + "\u2208": "\\in ", + "\u2209": "\\notin ", + "\u220b": "\\ni ", + "\u220c": "\\nni ", + # Ordinary symbols + "\u221e": "\\infty ", + # Binary relations + "\u00b1": "\\pm ", + "\u2213": "\\mp ", + # Italic, Latin, uppercase + "\U0001d434": "A", + "\U0001d435": "B", + "\U0001d436": "C", + "\U0001d437": "D", + "\U0001d438": "E", + "\U0001d439": "F", + "\U0001d43a": "G", + "\U0001d43b": "H", + "\U0001d43c": "I", + "\U0001d43d": "J", + "\U0001d43e": "K", + "\U0001d43f": "L", + "\U0001d440": "M", + "\U0001d441": "N", + "\U0001d442": "O", + "\U0001d443": "P", + "\U0001d444": "Q", + "\U0001d445": "R", + "\U0001d446": "S", + "\U0001d447": "T", + "\U0001d448": "U", + "\U0001d449": "V", + "\U0001d44a": "W", + "\U0001d44b": "X", + "\U0001d44c": "Y", + "\U0001d44d": "Z", + # Italic, Latin, lowercase + "\U0001d44e": "a", + "\U0001d44f": "b", + "\U0001d450": "c", + "\U0001d451": "d", + "\U0001d452": "e", + "\U0001d453": "f", + "\U0001d454": "g", + "\U0001d456": "i", + "\U0001d457": "j", + "\U0001d458": "k", + "\U0001d459": "l", + "\U0001d45a": "m", + "\U0001d45b": "n", + "\U0001d45c": "o", + "\U0001d45d": "p", + "\U0001d45e": "q", + "\U0001d45f": "r", + "\U0001d460": "s", + "\U0001d461": "t", + "\U0001d462": "u", + "\U0001d463": "v", + "\U0001d464": "w", + "\U0001d465": "x", + "\U0001d466": "y", + "\U0001d467": "z", +} + +FUNC = { + "sin": "\\sin({fe})", + "cos": "\\cos({fe})", + "tan": "\\tan({fe})", + "arcsin": "\\arcsin({fe})", + "arccos": "\\arccos({fe})", + "arctan": "\\arctan({fe})", + "arccot": "\\arccot({fe})", + "sinh": "\\sinh({fe})", + "cosh": "\\cosh({fe})", + "tanh": "\\tanh({fe})", + "coth": "\\coth({fe})", + "sec": "\\sec({fe})", + "csc": "\\csc({fe})", +} + +FUNC_PLACE = "{fe}" + +BRK = "\\\\" + +CHR_DEFAULT = { + "ACC_VAL": "\\hat{{{0}}}", +} + +POS = { + "top": "\\overline{{{0}}}", # not sure + "bot": "\\underline{{{0}}}", +} + +POS_DEFAULT = { + "BAR_VAL": "\\overline{{{0}}}", +} + +SUB = "_{{{0}}}" + +SUP = "^{{{0}}}" + +F = { + "bar": "\\frac{{{num}}}{{{den}}}", + "skw": r"^{{{num}}}/_{{{den}}}", + "noBar": "\\genfrac{{}}{{}}{{0pt}}{{}}{{{num}}}{{{den}}}", + "lin": "{{{num}}}/{{{den}}}", +} +F_DEFAULT = "\\frac{{{num}}}{{{den}}}" + +D = "\\left{left}{text}\\right{right}" + +D_DEFAULT = { + "left": "(", + "right": ")", + "null": ".", +} + +RAD = "\\sqrt[{deg}]{{{text}}}" + +RAD_DEFAULT = "\\sqrt{{{text}}}" + +ARR = "\\begin{{array}}{{c}}{text}\\end{{array}}" + +LIM_FUNC = { + "lim": "\\lim_{{{lim}}}", + "max": "\\max_{{{lim}}}", + "min": "\\min_{{{lim}}}", +} + +LIM_TO = ("\\rightarrow", "\\to") + +LIM_UPP = "\\overset{{{lim}}}{{{text}}}" + +M = "\\begin{{matrix}}{text}\\end{{matrix}}" diff --git a/packages/markitdown/src/markitdown/converter_utils/docx/math/omml.py b/packages/markitdown/src/markitdown/converter_utils/docx/math/omml.py new file mode 100644 index 0000000..03043a8 --- /dev/null +++ b/packages/markitdown/src/markitdown/converter_utils/docx/math/omml.py @@ -0,0 +1,400 @@ +# -*- coding: utf-8 -*- + +""" +Office Math Markup Language (OMML) +Adapted from https://github.com/xiilei/dwml/blob/master/dwml/omml.py +On 25/03/2025 +""" + +import xml.etree.ElementTree as ET + +from .latex_dict import ( + CHARS, + CHR, + CHR_BO, + CHR_DEFAULT, + POS, + POS_DEFAULT, + SUB, + SUP, + F, + F_DEFAULT, + T, + FUNC, + D, + D_DEFAULT, + RAD, + RAD_DEFAULT, + ARR, + LIM_FUNC, + LIM_TO, + LIM_UPP, + M, + BRK, + BLANK, + BACKSLASH, + ALN, + FUNC_PLACE, +) + +OMML_NS = "{http://schemas.openxmlformats.org/officeDocument/2006/math}" + + +def load(stream): + tree = ET.parse(stream) + for omath in tree.findall(OMML_NS + "oMath"): + yield oMath2Latex(omath) + + +def load_string(string): + root = ET.fromstring(string) + for omath in root.findall(OMML_NS + "oMath"): + yield oMath2Latex(omath) + + +def escape_latex(strs): + last = None + new_chr = [] + strs = strs.replace(r"\\", "\\") + for c in strs: + if (c in CHARS) and (last != BACKSLASH): + new_chr.append(BACKSLASH + c) + else: + new_chr.append(c) + last = c + return BLANK.join(new_chr) + + +def get_val(key, default=None, store=CHR): + if key is not None: + return key if not store else store.get(key, key) + else: + return default + + +class Tag2Method(object): + def call_method(self, elm, stag=None): + getmethod = self.tag2meth.get + if stag is None: + stag = elm.tag.replace(OMML_NS, "") + method = getmethod(stag) + if method: + return method(self, elm) + else: + return None + + def process_children_list(self, elm, include=None): + """ + process children of the elm,return iterable + """ + for _e in list(elm): + if OMML_NS not in _e.tag: + continue + stag = _e.tag.replace(OMML_NS, "") + if include and (stag not in include): + continue + t = self.call_method(_e, stag=stag) + if t is None: + t = self.process_unknow(_e, stag) + if t is None: + continue + yield (stag, t, _e) + + def process_children_dict(self, elm, include=None): + """ + process children of the elm,return dict + """ + latex_chars = dict() + for stag, t, e in self.process_children_list(elm, include): + latex_chars[stag] = t + return latex_chars + + def process_children(self, elm, include=None): + """ + process children of the elm,return string + """ + return BLANK.join( + ( + t if not isinstance(t, Tag2Method) else str(t) + for stag, t, e in self.process_children_list(elm, include) + ) + ) + + def process_unknow(self, elm, stag): + return None + + +class Pr(Tag2Method): + text = "" + + __val_tags = ("chr", "pos", "begChr", "endChr", "type") + + __innerdict = None # can't use the __dict__ + + """ common properties of element""" + + def __init__(self, elm): + self.__innerdict = {} + self.text = self.process_children(elm) + + def __str__(self): + return self.text + + def __unicode__(self): + return self.__str__(self) + + def __getattr__(self, name): + return self.__innerdict.get(name, None) + + def do_brk(self, elm): + self.__innerdict["brk"] = BRK + return BRK + + def do_common(self, elm): + stag = elm.tag.replace(OMML_NS, "") + if stag in self.__val_tags: + t = elm.get("{0}val".format(OMML_NS)) + self.__innerdict[stag] = t + return None + + tag2meth = { + "brk": do_brk, + "chr": do_common, + "pos": do_common, + "begChr": do_common, + "endChr": do_common, + "type": do_common, + } + + +class oMath2Latex(Tag2Method): + """ + Convert oMath element of omml to latex + """ + + _t_dict = T + + __direct_tags = ("box", "sSub", "sSup", "sSubSup", "num", "den", "deg", "e") + + def __init__(self, element): + self._latex = self.process_children(element) + + def __str__(self): + return self.latex + + def __unicode__(self): + return self.__str__(self) + + def process_unknow(self, elm, stag): + if stag in self.__direct_tags: + return self.process_children(elm) + elif stag[-2:] == "Pr": + return Pr(elm) + else: + return None + + @property + def latex(self): + return self._latex + + def do_acc(self, elm): + """ + the accent function + """ + c_dict = self.process_children_dict(elm) + latex_s = get_val( + c_dict["accPr"].chr, default=CHR_DEFAULT.get("ACC_VAL"), store=CHR + ) + return latex_s.format(c_dict["e"]) + + def do_bar(self, elm): + """ + the bar function + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["barPr"] + latex_s = get_val(pr.pos, default=POS_DEFAULT.get("BAR_VAL"), store=POS) + return pr.text + latex_s.format(c_dict["e"]) + + def do_d(self, elm): + """ + the delimiter object + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["dPr"] + null = D_DEFAULT.get("null") + s_val = get_val(pr.begChr, default=D_DEFAULT.get("left"), store=T) + e_val = get_val(pr.endChr, default=D_DEFAULT.get("right"), store=T) + return pr.text + D.format( + left=null if not s_val else escape_latex(s_val), + text=c_dict["e"], + right=null if not e_val else escape_latex(e_val), + ) + + def do_spre(self, elm): + """ + the Pre-Sub-Superscript object -- Not support yet + """ + pass + + def do_sub(self, elm): + text = self.process_children(elm) + return SUB.format(text) + + def do_sup(self, elm): + text = self.process_children(elm) + return SUP.format(text) + + def do_f(self, elm): + """ + the fraction object + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["fPr"] + latex_s = get_val(pr.type, default=F_DEFAULT, store=F) + return pr.text + latex_s.format(num=c_dict.get("num"), den=c_dict.get("den")) + + def do_func(self, elm): + """ + the Function-Apply object (Examples:sin cos) + """ + c_dict = self.process_children_dict(elm) + func_name = c_dict.get("fName") + return func_name.replace(FUNC_PLACE, c_dict.get("e")) + + def do_fname(self, elm): + """ + the func name + """ + latex_chars = [] + for stag, t, e in self.process_children_list(elm): + if stag == "r": + if FUNC.get(t): + latex_chars.append(FUNC[t]) + else: + raise NotImplemented("Not support func %s" % t) + else: + latex_chars.append(t) + t = BLANK.join(latex_chars) + return t if FUNC_PLACE in t else t + FUNC_PLACE # do_func will replace this + + def do_groupchr(self, elm): + """ + the Group-Character object + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["groupChrPr"] + latex_s = get_val(pr.chr) + return pr.text + latex_s.format(c_dict["e"]) + + def do_rad(self, elm): + """ + the radical object + """ + c_dict = self.process_children_dict(elm) + text = c_dict.get("e") + deg_text = c_dict.get("deg") + if deg_text: + return RAD.format(deg=deg_text, text=text) + else: + return RAD_DEFAULT.format(text=text) + + def do_eqarr(self, elm): + """ + the Array object + """ + return ARR.format( + text=BRK.join( + [t for stag, t, e in self.process_children_list(elm, include=("e",))] + ) + ) + + def do_limlow(self, elm): + """ + the Lower-Limit object + """ + t_dict = self.process_children_dict(elm, include=("e", "lim")) + latex_s = LIM_FUNC.get(t_dict["e"]) + if not latex_s: + raise NotImplemented("Not support lim %s" % t_dict["e"]) + else: + return latex_s.format(lim=t_dict.get("lim")) + + def do_limupp(self, elm): + """ + the Upper-Limit object + """ + t_dict = self.process_children_dict(elm, include=("e", "lim")) + return LIM_UPP.format(lim=t_dict.get("lim"), text=t_dict.get("e")) + + def do_lim(self, elm): + """ + the lower limit of the limLow object and the upper limit of the limUpp function + """ + return self.process_children(elm).replace(LIM_TO[0], LIM_TO[1]) + + def do_m(self, elm): + """ + the Matrix object + """ + rows = [] + for stag, t, e in self.process_children_list(elm): + if stag == "mPr": + pass + elif stag == "mr": + rows.append(t) + return M.format(text=BRK.join(rows)) + + def do_mr(self, elm): + """ + a single row of the matrix m + """ + return ALN.join( + [t for stag, t, e in self.process_children_list(elm, include=("e",))] + ) + + def do_nary(self, elm): + """ + the n-ary object + """ + res = [] + bo = "" + for stag, t, e in self.process_children_list(elm): + if stag == "naryPr": + bo = get_val(t.chr, store=CHR_BO) + else: + res.append(t) + return bo + BLANK.join(res) + + def do_r(self, elm): + """ + Get text from 'r' element,And try convert them to latex symbols + @todo text style support , (sty) + @todo \text (latex pure text support) + """ + _str = [] + for s in elm.findtext("./{0}t".format(OMML_NS)): + # s = s if isinstance(s,unicode) else unicode(s,'utf-8') + _str.append(self._t_dict.get(s, s)) + return escape_latex(BLANK.join(_str)) + + tag2meth = { + "acc": do_acc, + "r": do_r, + "bar": do_bar, + "sub": do_sub, + "sup": do_sup, + "f": do_f, + "func": do_func, + "fName": do_fname, + "groupChr": do_groupchr, + "d": do_d, + "rad": do_rad, + "eqArr": do_eqarr, + "limLow": do_limlow, + "limUpp": do_limupp, + "lim": do_lim, + "m": do_m, + "mr": do_mr, + "nary": do_nary, + } diff --git a/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py b/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py new file mode 100644 index 0000000..78552bc --- /dev/null +++ b/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py @@ -0,0 +1,156 @@ +import zipfile +from io import BytesIO +from typing import BinaryIO +from xml.etree import ElementTree as ET + +from bs4 import BeautifulSoup, Tag + +from .math.omml import OMML_NS, oMath2Latex + +MATH_ROOT_TEMPLATE = "".join( + ( + "', + "{0}", + ) +) + + +def _convert_omath_to_latex(tag: Tag) -> str: + """ + Converts an OMML (Office Math Markup Language) tag to LaTeX format. + + Args: + tag (Tag): A BeautifulSoup Tag object representing the OMML element. + + Returns: + str: The LaTeX representation of the OMML element. + """ + # Format the tag into a complete XML document string + math_root = ET.fromstring(MATH_ROOT_TEMPLATE.format(str(tag))) + # Find the 'oMath' element within the XML document + math_element = math_root.find(OMML_NS + "oMath") + # Convert the 'oMath' element to LaTeX using the oMath2Latex function + latex = oMath2Latex(math_element).latex + return latex + + +def _get_omath_tag_replacement(tag: Tag, block: bool = False) -> Tag: + """ + Creates a replacement tag for an OMML (Office Math Markup Language) element. + + Args: + tag (Tag): A BeautifulSoup Tag object representing the "oMath" element. + block (bool, optional): If True, the LaTeX will be wrapped in double dollar signs for block mode. Defaults to False. + + Returns: + Tag: A BeautifulSoup Tag object representing the replacement element. + """ + t_tag = Tag(name="w:t") + t_tag.string = ( + f"$${_convert_omath_to_latex(tag)}$$" + if block + else f"${_convert_omath_to_latex(tag)}$" + ) + r_tag = Tag(name="w:r") + r_tag.append(t_tag) + return r_tag + + +def _replace_equations(tag: Tag): + """ + Replaces OMML (Office Math Markup Language) elements with their LaTeX equivalents. + + Args: + tag (Tag): A BeautifulSoup Tag object representing the OMML element. Could be either "oMathPara" or "oMath". + + Raises: + ValueError: If the tag is not supported. + """ + if tag.name == "oMathPara": + # Create a new paragraph tag + p_tag = Tag(name="w:p") + # Replace each 'oMath' child tag with its LaTeX equivalent as block equations + for child_tag in tag.find_all("oMath"): + p_tag.append(_get_omath_tag_replacement(child_tag, block=True)) + # Replace the original 'oMathPara' tag with the new paragraph tag + tag.replace_with(p_tag) + elif tag.name == "oMath": + # Replace the 'oMath' tag with its LaTeX equivalent as inline equation + tag.replace_with(_get_omath_tag_replacement(tag, block=False)) + else: + raise ValueError(f"Not supported tag: {tag.name}") + + +def _pre_process_math(content: bytes) -> bytes: + """ + Pre-processes the math content in a DOCX -> XML file by converting OMML (Office Math Markup Language) elements to LaTeX. + This preprocessed content can be directly replaced in the DOCX file -> XMLs. + + Args: + content (bytes): The XML content of the DOCX file as bytes. + + Returns: + bytes: The processed content with OMML elements replaced by their LaTeX equivalents, encoded as bytes. + """ + soup = BeautifulSoup(content.decode(), features="xml") + for tag in soup.find_all("oMathPara"): + _replace_equations(tag) + for tag in soup.find_all("oMath"): + _replace_equations(tag) + return str(soup).encode() + + +def pre_process_docx(input_docx: BinaryIO) -> BinaryIO: + """ + Pre-processes a DOCX file with provided steps. + + The process works by unzipping the DOCX file in memory, transforming specific XML files + (such as converting OMML elements to LaTeX), and then zipping everything back into a + DOCX file without writing to disk. + + Args: + input_docx (BinaryIO): A binary input stream representing the DOCX file. + + Returns: + BinaryIO: A binary output stream representing the processed DOCX file. + """ + output_docx = BytesIO() + # The files that need to be pre-processed from .docx + pre_process_enable_files = [ + "word/document.xml", + "word/footnotes.xml", + "word/endnotes.xml", + ] + with zipfile.ZipFile(input_docx, mode="r") as zip_input: + files = {name: zip_input.read(name) for name in zip_input.namelist()} + with zipfile.ZipFile(output_docx, mode="w") as zip_output: + zip_output.comment = zip_input.comment + for name, content in files.items(): + if name in pre_process_enable_files: + try: + # Pre-process the content + updated_content = _pre_process_math(content) + # In the future, if there are more pre-processing steps, they can be added here + zip_output.writestr(name, updated_content) + except: + # If there is an error in processing the content, write the original content + zip_output.writestr(name, content) + else: + zip_output.writestr(name, content) + output_docx.seek(0) + return output_docx diff --git a/packages/markitdown/src/markitdown/converters/_docx_converter.py b/packages/markitdown/src/markitdown/converters/_docx_converter.py index a9c469f..b320695 100644 --- a/packages/markitdown/src/markitdown/converters/_docx_converter.py +++ b/packages/markitdown/src/markitdown/converters/_docx_converter.py @@ -3,6 +3,7 @@ import sys from typing import BinaryIO, Any from ._html_converter import HtmlConverter +from ..converter_utils.docx.pre_process import pre_process_docx from .._base_converter import DocumentConverter, DocumentConverterResult from .._stream_info import StreamInfo from .._exceptions import MissingDependencyException, MISSING_DEPENDENCY_MESSAGE @@ -72,6 +73,8 @@ class DocxConverter(HtmlConverter): ) style_map = kwargs.get("style_map", None) + pre_process_stream = pre_process_docx(file_stream) return self._html_converter.convert_string( - mammoth.convert_to_html(file_stream, style_map=style_map).value, **kwargs + mammoth.convert_to_html(pre_process_stream, style_map=style_map).value, + **kwargs, ) diff --git a/packages/markitdown/tests/test_files/equations.docx b/packages/markitdown/tests/test_files/equations.docx new file mode 100644 index 0000000000000000000000000000000000000000..6a05cd77f62aeb61624be36c05e12773177a8071 GIT binary patch literal 15235 zcmeHuWpEuyvhEQxGfS3aF|#a|EM|+D$zo<^W(JE{7BgDR%*@QpPcwJ-&8&BCyomSr z-agS4r@QLQrp&CYsxM_FKtNFe-~dPf06+v_jImNS0s;W?!2kdh03@)wpp~V)p{2dH zf{V4GohGfbg*jmkC@}dC0Psiq|K0uvzk%BLVY6;}#IN@OZ+?^Y3*yb>`8|U+rMJj1Xq zp0&<>B+ii=;|rJk;2`K^LjuGWk5fI_NRVKwPnt~2YQ#Sa(5rAJZ#L+u#( z!cRGr5nSvutY15qffzCeU|xS)#8prG7Bj;U$wOG4@DNpX@0P2y&us0Td~>Kg|2g({ z@tVIa7PSau(Y?hWi1$OY-rqq0vj1Y9_%YZG=N~oFAJzx+VV>HyhURv3w11TUYnK0m zZSil9UKZQ+VV1Cb=YFq#lWlTKU6{Gjbovv^n2QjQ>SB^8%L``nZ?9Yn3&5I(x+23< z({U57cIm0rek6PX8&#lgYgn-t32Io2JE!ecZJEPYjVlj##zc5v_ z&~a?=n1|rx(N2_IN};>mLKxFe6yr1cBsIC2Hj4B2$(~F`^$1Yh;WtqqwEUHm9c}O^?wlgT}sSojh{Q?I`RG3803` zh6mavNR*PJ9Tq`DI~sn5PkbkS-w$qbkNm z#w|L`^O`igh5>6j@)9s;%gf71*y1jV;qGsOeZ90f+uYsVB`v>pnkSF0Ak5~0f3B`D zsRk8?ArFW{Vy=2VeJcYCmgQP$j}-3=#CNZHP>7KVd+> zRY>5(7xJbYaDpsw?m~qiMx#S-2bpF0Tw!L?jK2TL7f+NTi4A-c?T0P&z6P719hA!T z81RHV9s(lxPZ(GaD@66vk_^sh8vr6fcJ_SsNL#jAiH;&jH9YWEnVIOFuBbZrJSXg6 z5pf-YAmg!UMt8K?p8pYeBr)dI5ZGr(`Zw`JO5mMqO3v+0B%QYv1b4@7hB_s1ke7>V zpm67(xIigqYmgRbx)|UEY1td?t)F!1PQ4QEF=pJv-fv>=z4@7{G&Gb0=Ba;BA4{Yj z&>r%L;YpOSBy@ToBL&S|=dOJDAvo!rpX8%)D&L6)UF~=*YSlPnX=f2*4+8@?WH4qI1@VMG*q1a{(fchYv?d}nZ>Avjg8E$Y_D;egk?l&^%0XX41Ta`+d7 z){(0?m*LI*kIdL#gA)CCBgS z7^A`DwLz(?b63BhG`tl%k4BAgzA}ViAt7J`1w|#Z0mFcEqn%>05xLbVJlqvD85 zmQ~H2Nk+(fD>i|8l>xrflVXaUdwnCR$v+5P+@W0Gnanau-qOw=mBUQ6s0(s6MJ=z$ z$60M9ZyM_GFd6*G%q&q;R@^>5VcC-BMa1^24rfWE4WUAtm*5q%g$mM>qT$79>`<=m z1@xV`4QuZ5O`bdec)asUOV!?JaY(G!c>V36RJ;&rM%+tVDpP@%{Ql5+4kOsyEpG3y2sH#? zMf1KRx-+-Sa72tvi!QTS-6-vb%Ay$asy=Sit`czhK{4g#O{i)7L>ZV#Asy)H-l z^5eRV1-P07EU=*3!AcE?p!aSQESb#IthkIHh}d;u=icdgY-AxGIgnv>WCk=(@{1v}aR`u6*(P>hrY1o0EjEfcM8;-Zcm3p`ryo>dwPte*H zb@L_%!eIBJsI1pS-Zh4So<$<s6+V|bNjTWTKqMRcjV?ey&6VP2PLKkQd16B1JvqRi^_7^?)O>tk&i=Y8JNpEC zj`;9C)6@0*@KDw6cF8Z=8>NT6KyH{q(+1BwhaqEQz>32u$WNdfNTbsP7%D<`Se5Wh zWBK(auGCrtlw62!NW>kYKAkVvxlmoAbMh9uq?0r!Pf~2gem_Y-Z)QUyBot{bZ299o(N@A|aAewcgrFR3wSB%Qe(s6IJJ>>_ zBb(>D2R5KzziG-}0$sgiZY8jx$B1t*_p4Kl7G>H)_}f+en^F0;B5W%-X;i`~3w-LU z)@SwA;l%O3t3QZ$Xx=}8Yxx2D<}R`}D_Jp{N!(yF<{OMdTg{nS zwl(+{7U_L%Mt&6C=~aV_qQDXLcBDmR2i8N%rNEWY_<|nqhrh?*0|U`RpQGW}qE$=+ z@^j1eII5m~g`b`{!#?6sDQOGjA$e1!HXTR4G|s2h{c`K%^17zKBZJ$;_U-|rGU}C2 zW#>+3$9Pxk{o!_f-3#dL={9IyrlV@x1^xYKpHBPrt-qrf1H9ZStIhT8WYO+e zOzY?dISj!TqK!37hVK{Vc$ivAZ{d?ul0cvy)ey%WJ4}93eg6BeIzOyv2z}5sAW|G# z`wf~afqsrh!Tk~eN`o;6T7wk;apt2qlr_?vmSRz!gT5YUkQv)kUBQEtF}HJKYhB6e zXFRKtGGyZe9~Jzh3GabQ1T=%0L1ci`5>=%;55i>>KV1yCXE@+9XBk9{3O5lCxU(jI zt*%6he`7VbvqrEduPQ#zMnR#t2Pa`o2*xyOh2IAbRO+KAQQhG|^@-S-^o@Y?vm@d@ zB^KcK@?~!rTrq<2JCh>6PMkf3FhEKb{d_SJxDpg^6vNGV3{D?jo^FF`P?dF=0xJgz zUtAgcV-H@=;t&FJxA_@gIIv}Wec8znyBZ=GJB4XEk#A-t3WhOYprCbBfVj6Ft6o^d z_zWkzEeg@6(ApQV@i_%fM!$QH;tHE{)cpvr(S>$V@?9T;r&qzDJW90*Hx!f|;FLD{b;- z2iu~){N4ke!G!woK+juWJ(hr;WHk5)qF$wyM1y>fwHIYheH?n4`fkBqfz+z(3c0HQ zZpJsDSGI(xuUmR%_+KtON-|pgt5vsmHd(3CXbn+bE-_fS*`wfNxFo12hMV`QD<0S9 zSzMkUt5ciGjA%=-S_UABjHZBvwL!{h-D(r*@Ar&Dp*WCXeD9iQJsMY@<9tqL;Am>w zO+rB)8`H-+A7PJ-JBuE0!#2phtI`Ydj4vML>&E#do2aww8x$L`39m1_r4#R+OU9<0 z(&Pc5wd>^-&xXaHW7pKU+aNVd=V`RH>nkA17aNFTQbtJR>Z6N(a*h*aD?1WVr=p|7 z*JubRNd(ekgepE*MBE{j@n#<(iiTKac%O(Y(ulG0tlBa3+CaEj$g@jztBVRbBI$%ejP>b8WN z)hxem!^t*(B??xyEYWJzJfYPguenGP>QIgT;Ow%w40(b<=k^^DYtYzx9 zWYu@M=nf{qwwH#vvpiAa)ok$8p;csx{1c?atihSjOO*`zu+x3B*YT@IBW2ys za8s4MW_VgN-PgDFo!yQO2o<$G-BBFo;Zf=G3F-!j0P&yGP0geUppqLXQ(pgD`u7>Jo8^oOusi>&E5~Srp}-J=UJqh zxb>?apa8%L+}}pYc82!$rk2Kbe{_De%Ij9ktcacjmG4~_*SN`vp@+$u1K%~L=d{&OogfHsw_hgYb!B)ui6k1;qtXE2d%1A5RAvc zEtXAFKk*A#2`Gr3BDm=IjO`7u$-#TH2g?SNqRthEkt43F3r65`F#h<>!eRCTntn{` zaTiffEE)SPB2B&3gMD8P;(_BZa<28hY=WhiaEU$2b!}neag#EO7y&mD8MH-u^c%*$ zjqoZ8`k|U2J85^-sb46YF%HJ$+JUp*X5s8boSyNVP6d=6N$Dq7}@* z4utE00^x3D@WBeFC19rEViXk|;peOOQpvgsRgQRV;VrG3?--2s>F3$q@X*-;HHh&?K^xt!C zt2^X5!*y8=Dz}nfN7X5SVvd@;20(hE0%+^@Wn_u(`=Ng_$g|`fMx>|2seF^S2u9Rc zN#vO6OXDE^>I243DEl3UoZ!<#*Ulq|FvrTY)lNg=1A%NH`t5l(H9?{V#TXB6DfiCP zy!6z}^Fyoq=6B*G$IBEu2YH8B6Xd|@&+-wD!TT*SH3x#gWRP>GXPLy=Oes={ZK69; zuMPyrqrmfNx1YYV!CF2=7av1(l^~?hkm<(X5QP;p-MTq7i@MXDMCfq9z4V-aHIlQQ z##w$hW=?rL%VDcmM5cXyU7>!pW;B(l5z9O5NMn^ofmm_8t8pBA6P4t9wO-b-ZT$j;{%jQrl zsyEAbUeOO9v?TR&1EnQmZDO8+lfPrgw<)%ER-x1^c4Y_1y^9`;Zn3R6tE*|5k zvAqq)Q?*4}Txhu^bt}K(?2U1=-M*l>qucILqWnc-w@9svn3GnR3-~qmNb3*DbJ*JD zuVmL{Cz9h0j2&@cO6d006GOkG(N$IR4Be6kfGheTFwq=~cfktd39R3;yS0N;Nd~w} zE=roXvs;~sO@4mka1OsjSs7&~gMLd>ATd4=)1KAJ8&$UmjcipKw`VCxDBXvBk|@TD z+&1nj=;;Xix_*S^W4nT{x}-U$EVU6YidlxXavTa1_Nq(I7HpTfdPEoVBSyrs8dB+$ z%SP~;`^U&hfZcm|7-=lK6~lLI)`E51euRS36kc0oGHjROP8^yW8Rirkynd+ye)`u2 z3;AcZ)2ILJ&aOJzuvJECCYOi3;~00T8t+qy&rm)G>8M3Qpq|0OO;C*?SZul0jY%T%{iQPFce4x-n z#gtGTj7Yg1Pp`37Lyg|L_r)eD-@C>pK5sPw7;i62)DI)=S3qLE5N%OjIZ z3Y^8K3dD_Cs3K~6hIKcKNI)aU4$i^EhnTG&C3iMk1fcjSOlXA;*@_+W;*v)IiZMdW zp+BXEKZqCi%O!8pMsFKr3?{aFD-$M=v^5k&Po-6{7tb4px<*r&$pxqY%>;JrssRh% zM@dA-(w0Tn2U7;p0Yz$)ss`C*=4jpFY3R0S(ke*WZ^G25YXv!`CNF=cSx@{7`xY*W zO4wG_7!FE+D2j`2y6m7CBu&DvS3xi`i*#e4DDey0U61_p&$)Wj`fLbkn~v2l!Z_o+ zWJ`1Ys|vx=`k^rA7G^S-=^JYoWizm`-(}2sus6a1{F0wNshNq*vB=L#)orRn4D&fg zE6Ani$mTK^KzKQs03@@Dko~isMf}AGDu$opZaCA|i1X~LdMIdHa!iAtLn8Q4+JLG! zbmIFpdA~{8YEHetJL~#!Q^RXh&1i%D49!gRh`5_-IEicK*6cEa@m%m9`pI$a6bf^& zLmjCF&w=4A(nU?o@v1?V#x`;{x|YwTVFT_n$XtBb7-Y699YN@7TQ z2Yd7W?xYOw+p87t3O8Npb9hn==)my)@I&upPKnZZRMfC@S5lcfr4m;X@7UKWgM=5g z6?{6?OT#u-1)AzE0T+%u+z9IR^efU`A@C` z#)N*aXGXed;zt-`!`o^})#E6b+w9w<#cf_)pnB{MBle(fy1Z_Y`AP|$VyKPF?PkQLb#3^> zDutZ+<;z|e^JWK)^IOx-pwTm(l|A7yT$RfH)wK!dq+EQ$;IS2hc5y3}*J#StO)lfW z%-XTNowdc=I`+xT)SOYrhNJ_}e5#N3g z=fsXIL@NFfpW0Fodvu`707$;er)Q)#UM^U^)u3%TV(flmR^~_%2@~8Xg$Z_i(jPWl zK9PNwJq+0WCaMF$)Lqfp}M030!fGj4CI3t0Mq31jeN<55kK5g6h z6IIl{X^c_Y|2F1x5A)o`5??ng9USw>#n2yrU~$KaGz=z#xeO62A$i0OIm>7kW>PxZD76~ zxph^l3soY%MLuO2o-^Y6qG3^njjxLX)G8JhD+zvD35xV02WO0yftq49J+uk8tOJ)c zYpL|fA~S%*B)@q>JT7p3?WZ%*a5bG@%vvxtiW671U~2`muHI$B;_u^u^uNU~Ims5Sy>F+x4c0J+V^Bh(csGP%Ek;X`(^0 z|<}Q|t$M^sYzl$;Eb(RQxF&WeR>W{b(Tpf0CdS)6CeLZ0z&(7thZsF%v&L zC>xsy^0$P7=gpfK{osKu23wuUt`WLSM7Ljq(27$ra*$`?ppuy^R4R-+uAQArp21-xZ(U_pBWA z*}%Yrp7Jv@CK?=ecqj;*uz2Ui7kk{w&4xpEo$m8l1|1xt*x%0rMC9w0mAmj9bHLX{T3;-=L_-0@(Q*q;T#F`;^CcdpSCWB^ z&+bDiBN(3~oHsehI?BtEevay6wT#Y>9|Q5Np<&g9||TCMoFV4YN>}bfe;S%q(02{GUP~2eibWH&Yt^*QBhwVz8=*!9YK)ew00?YBhBX-td%nGJyKlEGk&dU5MlC* z*dx)zBJ>?(qdC~A%_8(tkO5UNa%?$xIPeQ%TM&pRWUVX_s9F(-FtjIRysVE)v@8*{ zS`i{JjRK?(Iwh|6ra$zljX=O1kw5_XAG=Amz&{}WXk)gqu=X9FL?PH0ozkBV22J2c zKuLcX$o~uW(H06V83GC|=SKiE(S$$%qDgkD2wZo-|KoE133n70@c(f=%}Y}{xhKmS z-rjLn*YkyQufrkMBjtNJOANCaf)dLFg3|XI$Rq*x5H$iW+(Xw~0#VLk`Ru|X?xa~w z4G#&>qhI^u%BwODjRzpw%?4Qk6Z4Wl4~;)blfPj(1+;#L3T?Gf&TC23nc+AaYOf$Y z0PoinQ$DV4r}*k9(%DEg%HCIqe_dgInk_ekUbT4F#kBQl%*DRxifU^tT?n=Zl=6g5i=ldH;;oVnYxh`uCy zq*5UWW;V(@ANv!i9GuRt;y~Fh1;LLRUA-Q8IHZqSGGkr=#96@SbTddaNfv+TTx>@} zl@cQ0M6+P;d{>>IlK`kfYyq%uaM{0#0{y;F&msazun2X2fPIU=={G-w>68#DV9x#! z7ykB#Kta%tCPcwz02JvDX$9B<4szwja`i>R3@6Fk;JT4S6@reiHiL@{utkZu$DL+_ z{E%gdgvu=Yjb?+Sqfrw{+`IzX*sqY}JZ<#Odatr3fv>W<3fm?5tR>w6Mg4b;(_bO^|J&*h>{1c_0+E~Uui`#MiHwhiq`V&@ ziX+T4>Sl!hDaYlXm499&HtSz1>*W6J4i5d8#h|o+9NG-NqD{8ZozYNXpy9mP{u+1b zl8Eux6%%qRIhQkbac`)5N%%D6bB?U0P@h2!Lo4o< z3!~R!Xk>GRWvWadon>slplEY-3WUWh^i+*Tv)z=-IOvuv*9v@ZH%n60HsQCrbTf7l!clgoH* zqiRwTh0B=R*Er5=#8Z87-=}++7O|17^$7JLBVfLW)FH(Jr2G309G#msjFfXkd)!5d z<{8va4}SY#S&kaZg`?e@dY^2@yTH)4n2^s3eNkKrv@4>;(>&p*Gz3qgVx#ZM1rCDS zMmj?2Zp6RVeh8x=?XQannIv$vuQ1SqkEk_RFz5_Bq+%v>U18Frg+~BcL7J%IGE=1U zetUNW5ul-~g9=JhpizI3zh@B`viu1E1-WlfqTrv zmtce8k?Y3=Y?La)!p{T<>Oq;Eg4KF^OYUXqC{=oe5FG??G1oeKE$&>B!OoT6?7g5I zCdLg2DH!6UdBh8Z;ul=1MJOY9GeDWHw~04>gF%mfb@h&uL8p{JA;*sPN6a~vn@N_`|0p84&NUN_@20! z02RKE-_?ef#x!O8n&%ki_P%IDt!c&4@LK+*nRWRfX5Z7pplvB5$%j zmJl&n*CIUD?m9zD38v^W3x<8>`4f496=$pFu2JjTOY*Z78Uz?7HTvcp zJaX?(mBO0fRvNS7{jv${=})Q)R55<872F21igBY)#4YVpGFIlEYZ%=VXvAN<0-*;K zv^VAF3ujtDEyx`sc3lG{?56I(+(y;k9c6G@(GfAoX^?!5$nQn^fvl#h+f8IFmP`9i zV4`>^#<(?<3vdEvkV=Pz-dX0q-g@STleD}&5FDX@x#jOm8X{BkSUUBY-Wo-lf#8Xa z_2I7Z7*{N^`do$^p($zq{RT|)TI!sU`SHDY`Uhbor=XLGQzdCEH`4Qg{dx*9+d1Ja zhjmT=1huO6k)YvV3P;JU5=ET2W3Li(JBZ)F)7vm!)LhmO!8^o%nrisG_9SZc!40AL zSOr1-=Tw7}q29N@ErO&^uUh^hLZFbIUJ zN_%#`=b+U61k#G&)l5R(XPCO5chf#Z?$8Q4x`IeWuUpMmnu4I&lAXGET7hjngLZ98 zlW+sUDLsv4iscsO`;~};i+J6XzJ0I|T>){jQC{gRZ??gNMNLk1XG23JN=Zb9$#iVQ z#1_nSkcN}`g`T>gyeM1XiQM8-`J1;XuZ}y|2>Z!1R#Z8^pwWCxP8QRbFY@;3?`YFN zJrx)Xp6s(oX~}C<=@&UC{TXAyeHweJgBmUr=p|X~hr#_Y1<_V5pKq3H53#rooC8yh zf8zKhmMcd8Dwq7#D~|oa&U@@BfYMzL@3%1Z6X+#8S^CtfUDi5t1)*e;MLh>k9nmdG zQMSIWB@m>=b=6Dv@%sOrqp=RB%_93ja^{2j+nlb^M=pYbuAaH!p9GEg<;W#^#K3c^ zTfCeVItDQv)!ND<{36#$Ern$W`bfdDh|>XmX{+@rJVL5w8r3={unWD%WWN|SQ5tX?AfK@UIqV2}sWC`+SYOw97 z)N)egxqY(0dC?hY%E?Zj`ZoXi*#eVrBV2%6qQiz52XOck&=gZ=@}(wkU!EaJ^5SlA4uP@zPH;}Qi#7uuU)5QFXpJD=+1At-YI1YA z+ituyxh>n`e6)Zgma4L6q-%HvSe5cciKM~j6jn~)`>8g;!2)e1ah&F|82@irn>t_d zPdqPQ8^aJD$BxGmS;@a+{P=$DXqS7cAQ$pOCSrDDH;@v}J(vt9bTn7&-ak5>z2z5M z6v{neX%WyF;wC@xPOP}xIRd3Q7$3Wdzu_$Zx-78<%qCYe?|F>~>XY^RXn+U7ib2a(fKw=84xbX0a1f)Jy zI+5`GJDZ(BtgG6K3{Vk#*{00!vvJ&}#~NZEyC&i+jyHbjr$Cf0pE#67UWZ>8&!vm( zk^)(agdWD-=b|^{KZ+0E-Ya?#6lkle52)&9t@?mS8jmYLs&gFyILS3vrpntkxgWNodi?dbHaZ2wfh54!#T^2Z$|pjMd9p$4{CNIYGj7*d z)tNx2tZc^DKhE3vhcjv35c-ad$95Ld_@{5aXbHYxrk$~duI};Hc-KRnZNA>hZ_{4y2~Dw zQ*iohphA=LXXkH{J zfZ8X=%z6gq(ch=d{`%n`|6OxsQ9jApe`xN|M=j#tHCNZ#`VYVPzl!^zwI5r${IK)~ zF|w8T5<%{)eEQRmlKKi+sve)DnTbpqw>TB{dP5wc_e{8KqF@}J(-Bt@rtjTPxh#uc z0*f*zj7{>tEzOm_Y=EkaiGLdi-8PGZTd(qYLITPGeJqi=tb*}@>j}Eec-1*M@u?C?8q4!9MfJ& z5|MGm6*q4~f_l_vGc6d(M_=d^IEr&Rv#W|!@F&nMz1z;Gek9flLn1;`wBQ)_umR6Dwg z@QBd;`qYxIGVa$#;MBEva&V_7djD)Ruk?1)~`@@t)%UAcPm(L>3}%GA^gJ2YQ6Oww4ddr9%Mq@vrmvpQ~(-(MI%kbS)G z|I$<-V49DB>py23{`pz{R{V$DLs^M`74Wb5P=CV$vOXO5-?F2A2mYQY@)xw@gBkj7 zX(PYG|CNdS7Z?D@fc*#jzu_hSF6nnN>R+;U;Qt$H>hB_cr}O+JV)ujI`scI!o!s*~ z{P*Sazu>jl|A7C?V*2m+-6D0{;%%#@9O+ M`p{5w_CJpP58I?#egFUf literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_module_misc.py b/packages/markitdown/tests/test_module_misc.py index 33e2c44..1819183 100644 --- a/packages/markitdown/tests/test_module_misc.py +++ b/packages/markitdown/tests/test_module_misc.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 -m pytest import io import os +import re import shutil import openai import pytest @@ -262,6 +263,19 @@ def test_docx_comments() -> None: validate_strings(result, DOCX_COMMENT_TEST_STRINGS) +def test_docx_equations() -> None: + markitdown = MarkItDown() + docx_file = os.path.join(TEST_FILES_DIR, "equations.docx") + result = markitdown.convert(docx_file) + + # Check for inline equation m=1 (wrapped with single $) is present + assert "$m=1$" in result.text_content, "Inline equation $m=1$ not found" + + # Find block equations wrapped with double $$ and check if they are present + block_equations = re.findall(r"\$\$(.+?)\$\$", result.text_content) + assert block_equations, "No block equations found in the document." + + def test_input_as_strings() -> None: markitdown = MarkItDown()

    W=>v3T&vY5%Zy%Tz{Xx<^cn-Ql1?!fiCz)o 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 z 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≫36(kU0_%e!+U+KH1HKX`$Mzyr9Q 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 literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_files/test_notebook.ipynb b/packages/markitdown/tests/test_files/test_notebook.ipynb index 62db0fa..28a546f 100644 --- a/packages/markitdown/tests/test_files/test_notebook.ipynb +++ b/packages/markitdown/tests/test_files/test_notebook.ipynb @@ -1,89 +1,89 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "0f61db80", - "metadata": {}, - "source": [ - "# Test Notebook" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "3f2a5bbd", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "markitdown\n" - ] - } - ], - "source": [ - "print('markitdown')" - ] - }, - { - "cell_type": "markdown", - "id": "9b9c0468", - "metadata": {}, - "source": [ - "## Code Cell Below" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "37d8088a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "42\n" - ] - } - ], - "source": [ - "# comment in code\n", - "print(42)" - ] - }, - { - "cell_type": "markdown", - "id": "2e3177bd", - "metadata": {}, - "source": [ - "End\n", - "\n", - "---" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.8" - }, - "title": "Test Notebook Title" - }, - "nbformat": 4, - "nbformat_minor": 5 + "cells": [ + { + "cell_type": "markdown", + "id": "0f61db80", + "metadata": {}, + "source": [ + "# Test Notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "3f2a5bbd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "markitdown\n" + ] + } + ], + "source": [ + "print(\"markitdown\")" + ] + }, + { + "cell_type": "markdown", + "id": "9b9c0468", + "metadata": {}, + "source": [ + "## Code Cell Below" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "37d8088a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "42\n" + ] + } + ], + "source": [ + "# comment in code\n", + "print(42)" + ] + }, + { + "cell_type": "markdown", + "id": "2e3177bd", + "metadata": {}, + "source": [ + "End\n", + "\n", + "---" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.8" + }, + "title": "Test Notebook Title" + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/packages/markitdown/tests/test_markitdown.py b/packages/markitdown/tests/test_markitdown.py index 0a3b56e..8c34da0 100644 --- a/packages/markitdown/tests/test_markitdown.py +++ b/packages/markitdown/tests/test_markitdown.py @@ -2,13 +2,20 @@ import io import os import shutil +import openai import pytest import requests -from warnings import catch_warnings, resetwarnings +import warnings -from markitdown import MarkItDown, UnsupportedFormatException, FileConversionException +from markitdown import ( + MarkItDown, + UnsupportedFormatException, + FileConversionException, + StreamInfo, +) +from markitdown._stream_info import _guess_stream_info_from_stream skip_remote = ( True if os.environ.get("GITHUB_ACTIONS") else False @@ -35,6 +42,13 @@ JPG_TEST_EXIFTOOL = { "DateTimeOriginal": "2024:03:14 22:10:00", } +MP3_TEST_EXIFTOOL = { + "Title": "f67a499e-a7d0-4ca3-a49b-358bd934ae3e", + "Artist": "Artist Name Test String", + "Album": "Album Name Test String", + "SampleRate": "48000", +} + PDF_TEST_URL = "https://arxiv.org/pdf/2308.08155v2.pdf" PDF_TEST_STRINGS = [ "While there is contemporaneous exploration of multi-agent approaches" @@ -162,6 +176,107 @@ def validate_strings(result, expected_strings, exclude_strings=None): assert string not in text_content +def test_stream_info_operations() -> None: + """Test operations performed on StreamInfo objects.""" + + stream_info_original = StreamInfo( + mimetype="mimetype.1", + extension="extension.1", + charset="charset.1", + filename="filename.1", + local_path="local_path.1", + url="url.1", + ) + + # Check updating all attributes by keyword + keywords = ["mimetype", "extension", "charset", "filename", "local_path", "url"] + for keyword in keywords: + updated_stream_info = stream_info_original.copy_and_update( + **{keyword: f"{keyword}.2"} + ) + + # Make sure the targted attribute is updated + assert getattr(updated_stream_info, keyword) == f"{keyword}.2" + + # Make sure the other attributes are unchanged + for k in keywords: + if k != keyword: + assert getattr(stream_info_original, k) == getattr( + updated_stream_info, k + ) + + # Check updating all attributes by passing a new StreamInfo object + keywords = ["mimetype", "extension", "charset", "filename", "local_path", "url"] + for keyword in keywords: + updated_stream_info = stream_info_original.copy_and_update( + StreamInfo(**{keyword: f"{keyword}.2"}) + ) + + # Make sure the targted attribute is updated + assert getattr(updated_stream_info, keyword) == f"{keyword}.2" + + # Make sure the other attributes are unchanged + for k in keywords: + if k != keyword: + assert getattr(stream_info_original, k) == getattr( + updated_stream_info, k + ) + + # Check mixing and matching + updated_stream_info = stream_info_original.copy_and_update( + StreamInfo(extension="extension.2", filename="filename.2"), + mimetype="mimetype.3", + charset="charset.3", + ) + assert updated_stream_info.extension == "extension.2" + assert updated_stream_info.filename == "filename.2" + assert updated_stream_info.mimetype == "mimetype.3" + assert updated_stream_info.charset == "charset.3" + assert updated_stream_info.local_path == "local_path.1" + assert updated_stream_info.url == "url.1" + + # Check multiple StreamInfo objects + updated_stream_info = stream_info_original.copy_and_update( + StreamInfo(extension="extension.4", filename="filename.5"), + StreamInfo(mimetype="mimetype.6", charset="charset.7"), + ) + assert updated_stream_info.extension == "extension.4" + assert updated_stream_info.filename == "filename.5" + assert updated_stream_info.mimetype == "mimetype.6" + assert updated_stream_info.charset == "charset.7" + assert updated_stream_info.local_path == "local_path.1" + assert updated_stream_info.url == "url.1" + + +def test_stream_info_guesses() -> None: + """Test StreamInfo guesses based on stream content.""" + + test_tuples = [ + ( + os.path.join(TEST_FILES_DIR, "test.xlsx"), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ), + ( + os.path.join(TEST_FILES_DIR, "test.docx"), + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ), + ( + os.path.join(TEST_FILES_DIR, "test.pptx"), + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ), + (os.path.join(TEST_FILES_DIR, "test.xls"), "application/vnd.ms-excel"), + ] + + for file_path, expected_mimetype in test_tuples: + with open(file_path, "rb") as f: + guesses = _guess_stream_info_from_stream( + f, filename_hint=os.path.basename(file_path) + ) + assert len(guesses) > 0 + assert guesses[0].mimetype == expected_mimetype + assert guesses[0].extension == os.path.splitext(file_path)[1] + + @pytest.mark.skipif( skip_remote, reason="do not run tests that query external urls", @@ -183,7 +298,6 @@ def test_markitdown_remote() -> None: assert test_string in result.text_content # Youtube - # TODO: This test randomly fails for some reason. Haven't been able to repro it yet. Disabling until I can debug the issue result = markitdown.convert(YOUTUBE_TEST_URL) for test_string in YOUTUBE_TEST_STRINGS: assert test_string in result.text_content @@ -192,6 +306,10 @@ def test_markitdown_remote() -> None: def test_markitdown_local() -> None: markitdown = MarkItDown() + # Test PDF processing + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.pdf")) + validate_strings(result, PDF_TEST_STRINGS) + # Test XLSX processing result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.xlsx")) validate_strings(result, XLSX_TEST_STRINGS) @@ -230,10 +348,6 @@ def test_markitdown_local() -> None: ) validate_strings(result, BLOG_TEST_STRINGS) - # Test ZIP file processing - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_files.zip")) - validate_strings(result, XLSX_TEST_STRINGS) - # Test Wikipedia processing result = markitdown.convert( os.path.join(TEST_FILES_DIR, "test_wikipedia.html"), url=WIKIPEDIA_TEST_URL @@ -254,24 +368,135 @@ def test_markitdown_local() -> None: for test_string in RSS_TEST_STRINGS: assert test_string in text_content - ## Test non-UTF-8 encoding - result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_mskanji.csv")) - validate_strings(result, CSV_CP932_TEST_STRINGS) - # Test MSG (Outlook email) processing result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_outlook_msg.msg")) validate_strings(result, MSG_TEST_STRINGS) + # Test non-UTF-8 encoding + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_mskanji.csv")) + validate_strings(result, CSV_CP932_TEST_STRINGS) + # Test JSON processing result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test.json")) validate_strings(result, JSON_TEST_STRINGS) + # # Test ZIP file processing + result = markitdown.convert(os.path.join(TEST_FILES_DIR, "test_files.zip")) + validate_strings(result, DOCX_TEST_STRINGS) + validate_strings(result, XLSX_TEST_STRINGS) + validate_strings(result, BLOG_TEST_STRINGS) + + # Test input from a stream + input_data = b"

    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 G1N&c;K{7W0 delta 13265 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 diff --git a/packages/markitdown/tests/test_files/test.wav b/packages/markitdown/tests/test_files/test.wav new file mode 100644 index 0000000000000000000000000000000000000000..e872f106f0a6cbae805b5b0fc9d3adc4f4cb2602 GIT binary patch literal 1237070 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

Xg#7!cD{08z@9bP7FY}pF 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 literal 0 HcmV?d00001 diff --git a/packages/markitdown/tests/test_files/test.pdf b/packages/markitdown/tests/test_files/test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e82861e7dcc25c24544cf439a5d585236b35ce1b GIT binary patch literal 92971 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