mdengine 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mdengine-0.3.0 → mdengine-0.4.0}/PKG-INFO +54 -7
- {mdengine-0.3.0 → mdengine-0.4.0}/README.md +34 -5
- {mdengine-0.3.0 → mdengine-0.4.0}/pyproject.toml +31 -2
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/__init__.py +1 -1
- mdengine-0.4.0/src/md_generator/media/youtube/__init__.py +34 -0
- mdengine-0.4.0/src/md_generator/media/youtube/api/__init__.py +1 -0
- mdengine-0.4.0/src/md_generator/media/youtube/api/main.py +140 -0
- mdengine-0.4.0/src/md_generator/media/youtube/api/mcp_server.py +31 -0
- mdengine-0.4.0/src/md_generator/media/youtube/api/mcp_setup.py +48 -0
- mdengine-0.4.0/src/md_generator/media/youtube/api/run.py +30 -0
- mdengine-0.4.0/src/md_generator/media/youtube/api/settings.py +24 -0
- mdengine-0.4.0/src/md_generator/media/youtube/converter.py +140 -0
- mdengine-0.4.0/src/md_generator/media/youtube/formatter.py +58 -0
- mdengine-0.4.0/src/md_generator/media/youtube/metadata.py +210 -0
- mdengine-0.4.0/src/md_generator/media/youtube/service.py +200 -0
- mdengine-0.4.0/src/md_generator/media/youtube/transcript.py +100 -0
- mdengine-0.4.0/src/md_generator/playwright/__init__.py +6 -0
- mdengine-0.4.0/src/md_generator/playwright/api/__init__.py +1 -0
- mdengine-0.4.0/src/md_generator/playwright/api/convert_runner.py +71 -0
- mdengine-0.4.0/src/md_generator/playwright/api/jobs.py +92 -0
- mdengine-0.4.0/src/md_generator/playwright/api/main.py +295 -0
- mdengine-0.4.0/src/md_generator/playwright/api/mcp_server.py +37 -0
- mdengine-0.4.0/src/md_generator/playwright/api/mcp_setup.py +58 -0
- mdengine-0.4.0/src/md_generator/playwright/api/run.py +29 -0
- mdengine-0.4.0/src/md_generator/playwright/api/settings.py +26 -0
- mdengine-0.4.0/src/md_generator/playwright/assets.py +119 -0
- mdengine-0.4.0/src/md_generator/playwright/chunker.py +53 -0
- mdengine-0.4.0/src/md_generator/playwright/cli.py +131 -0
- mdengine-0.4.0/src/md_generator/playwright/extractor.py +24 -0
- mdengine-0.4.0/src/md_generator/playwright/html_to_md.py +25 -0
- mdengine-0.4.0/src/md_generator/playwright/options.py +35 -0
- mdengine-0.4.0/src/md_generator/playwright/pipeline.py +65 -0
- mdengine-0.4.0/src/md_generator/playwright/playwright_fetcher.py +107 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/mdengine.egg-info/PKG-INFO +54 -7
- {mdengine-0.3.0 → mdengine-0.4.0}/src/mdengine.egg-info/SOURCES.txt +29 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/mdengine.egg-info/entry_points.txt +6 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/mdengine.egg-info/requires.txt +20 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/LICENSE +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/setup.cfg +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/convert_runner.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/jobs.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/mcp_setup.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/query_options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/convert_impl.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/archive/options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/query_options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/staging.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/api/zip_bundle.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/backends/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/backends/base.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/backends/easy.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/backends/paddle.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/backends/tesseract.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/convert_impl.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/emit.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/io_util.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/image/utils.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/api/mcp_setup.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/api/run.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/formatter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/audio/service.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/document_converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/job_store.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/api/mcp_setup.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/api/run.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/formatter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/video/service.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/media/whisper_language.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/api/zip_bundle.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/md_emit.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/pdf_extract.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/pdf/utils.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/convert_runner.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/jobs.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/mcp_setup.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/query_options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/convert_impl.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/embedded_extract.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/ooxml_media.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/post_assets.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/text_formatting.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/vendor_pdf_md/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/vendor_pdf_md/convert.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/vendor_word_md/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/vendor_word_md/convert.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/ppt/zip_deep.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/convert_runner.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/jobs.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/mcp_setup.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/query_options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/convert_impl.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/format_detect.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/md_emit_json.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/md_emit_txt.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/md_emit_xml.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/text/options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/convert_runner.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/jobs.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/mcp_setup.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/query_options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/api/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/assets.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/convert_impl.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/crawl.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/extract.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/fetch.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/html_to_md.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/options.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/page_convert.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/post_convert_assets.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/url/robots_util.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/api/convert_util.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/api/jobs.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/api/main.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/api/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/artifact.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/word/settings.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/api/__init__.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/api/app.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/convert_config.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/converter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/converter_core.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/excel_reader.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/markdown_emitter.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/md_generator/xlsx/mcp_server.py +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/mdengine.egg-info/dependency_links.txt +0 -0
- {mdengine-0.3.0 → mdengine-0.4.0}/src/mdengine.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mdengine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, and web URLs to Markdown.
|
|
5
5
|
Author: mdengine contributors
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/vishal7090/md-generator
|
|
8
8
|
Project-URL: Repository, https://github.com/vishal7090/md-generator
|
|
9
9
|
Project-URL: Issues, https://github.com/vishal7090/md-generator/issues
|
|
10
|
-
Keywords: markdown,pdf,docx,pptx,xlsx,ocr,zip,url,html
|
|
10
|
+
Keywords: markdown,pdf,docx,pptx,xlsx,ocr,zip,url,html,youtube,playwright
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
13
|
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
@@ -74,6 +74,19 @@ Requires-Dist: imageio-ffmpeg>=0.5.1; extra == "audio"
|
|
|
74
74
|
Provides-Extra: video
|
|
75
75
|
Requires-Dist: openai-whisper>=20231117; extra == "video"
|
|
76
76
|
Requires-Dist: imageio-ffmpeg>=0.5.1; extra == "video"
|
|
77
|
+
Provides-Extra: youtube
|
|
78
|
+
Requires-Dist: youtube-transcript-api>=0.6.0; extra == "youtube"
|
|
79
|
+
Requires-Dist: httpx>=0.27.0; extra == "youtube"
|
|
80
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == "youtube"
|
|
81
|
+
Requires-Dist: lxml>=5.0.0; extra == "youtube"
|
|
82
|
+
Provides-Extra: playwright
|
|
83
|
+
Requires-Dist: playwright>=1.49.0; extra == "playwright"
|
|
84
|
+
Requires-Dist: httpx>=0.27.0; extra == "playwright"
|
|
85
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == "playwright"
|
|
86
|
+
Requires-Dist: lxml>=5.0.0; extra == "playwright"
|
|
87
|
+
Requires-Dist: markdownify>=0.14.0; extra == "playwright"
|
|
88
|
+
Requires-Dist: readability-lxml>=0.8.1; extra == "playwright"
|
|
89
|
+
Requires-Dist: Pillow>=10.0.0; extra == "playwright"
|
|
77
90
|
Provides-Extra: api
|
|
78
91
|
Requires-Dist: fastapi>=0.115.0; extra == "api"
|
|
79
92
|
Requires-Dist: uvicorn[standard]>=0.32.0; extra == "api"
|
|
@@ -86,9 +99,13 @@ Requires-Dist: fastmcp>=2.3.0; extra == "mcp"
|
|
|
86
99
|
Provides-Extra: dev
|
|
87
100
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
88
101
|
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
102
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == "dev"
|
|
103
|
+
Requires-Dist: lxml>=5.0.0; extra == "dev"
|
|
104
|
+
Requires-Dist: youtube-transcript-api>=0.6.0; extra == "dev"
|
|
89
105
|
Requires-Dist: mdengine[api]; extra == "dev"
|
|
90
106
|
Requires-Dist: mdengine[mcp]; extra == "dev"
|
|
91
107
|
Requires-Dist: mdengine[url]; extra == "dev"
|
|
108
|
+
Requires-Dist: mdengine[playwright]; extra == "dev"
|
|
92
109
|
Provides-Extra: all
|
|
93
110
|
Requires-Dist: pymupdf>=1.24.0; extra == "all"
|
|
94
111
|
Requires-Dist: pdfplumber>=0.11.0; extra == "all"
|
|
@@ -114,6 +131,7 @@ Requires-Dist: fastmcp>=2.3.0; extra == "all"
|
|
|
114
131
|
Requires-Dist: beautifulsoup4>=4.12.0; extra == "all"
|
|
115
132
|
Requires-Dist: lxml_html_clean>=0.4.0; extra == "all"
|
|
116
133
|
Requires-Dist: readability-lxml>=0.8.1; extra == "all"
|
|
134
|
+
Requires-Dist: youtube-transcript-api>=0.6.0; extra == "all"
|
|
117
135
|
Dynamic: license-file
|
|
118
136
|
|
|
119
137
|
# mdengine
|
|
@@ -238,10 +256,13 @@ If the shell reports “command not found”, ensure the Python **Scripts** dire
|
|
|
238
256
|
| `md-url` | `md_generator.url.converter:main` | `md-url https://example.com/doc ./web-out --artifact-layout` |
|
|
239
257
|
| `md-audio` | `md_generator.media.audio.converter:main` | `md-audio clip.mp3 transcript.md --model base` |
|
|
240
258
|
| `md-video` | `md_generator.media.video.converter:main` | `md-video clip.mp4 transcript.md --model base` |
|
|
259
|
+
| `md-youtube` | `md_generator.media.youtube.converter:main` | `md-youtube "https://youtu.be/…" out.md --transcript-lang en` |
|
|
241
260
|
| `md-audio-api` | `md_generator.media.audio.api.run:main` | REST + MCP on port **8011** (see [Audio and video to Markdown](#audio-and-video-to-markdown)) |
|
|
242
261
|
| `md-video-api` | `md_generator.media.video.api.run:main` | REST + MCP on port **8012** |
|
|
262
|
+
| `md-youtube-api` | `md_generator.media.youtube.api.run:main` | REST + MCP on port **8013** (JSON `url` body; see same section) |
|
|
243
263
|
| `md-audio-mcp` | `md_generator.media.audio.api.mcp_server:main` | Standalone MCP (`--transport stdio` \| `sse` \| `streamable-http`) |
|
|
244
264
|
| `md-video-mcp` | `md_generator.media.video.api.mcp_server:main` | Same for video |
|
|
265
|
+
| `md-youtube-mcp` | `md_generator.media.youtube.api.mcp_server:main` | Same for YouTube (`youtube_url_to_markdown`) |
|
|
245
266
|
|
|
246
267
|
Every command accepts **`-h` / `--help`** for full flags (artifact layout, OCR, ZIP options, etc.).
|
|
247
268
|
|
|
@@ -455,6 +476,7 @@ Library code lives under **`md_generator.media`**: shared probing in [`document_
|
|
|
455
476
|
```bash
|
|
456
477
|
pip install "mdengine[audio,api,mcp]" # audio CLI + HTTP + MCP
|
|
457
478
|
pip install "mdengine[video,api,mcp]" # video CLI + HTTP + MCP (same ML stack as audio)
|
|
479
|
+
pip install "mdengine[youtube,api,mcp]" # YouTube URL → Markdown (captions + metadata; optional Whisper fallback)
|
|
458
480
|
```
|
|
459
481
|
|
|
460
482
|
### Python library
|
|
@@ -483,6 +505,18 @@ md = svc.to_markdown(Path("input.mp4"), title=None)
|
|
|
483
505
|
svc.write_markdown(Path("input.mp4"), Path("out/transcript.md"))
|
|
484
506
|
```
|
|
485
507
|
|
|
508
|
+
**YouTube** — captions API + page metadata (BeautifulSoup / oEmbed); optional **yt-dlp + Whisper** when captions are missing (`mdengine[audio]` and `yt-dlp` on `PATH`, or `MD_YOUTUBE_YTDLP`):
|
|
509
|
+
|
|
510
|
+
```python
|
|
511
|
+
from md_generator.media.youtube import YouTubeToMarkdownService
|
|
512
|
+
|
|
513
|
+
svc = YouTubeToMarkdownService(whisper_model="base")
|
|
514
|
+
md = svc.to_markdown("https://www.youtube.com/watch?v=VIDEO_ID", transcript_languages=["en"])
|
|
515
|
+
svc.write_markdown("https://youtu.be/VIDEO_ID", Path("out/youtube.md"))
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
For file-based pipelines, [`YouTubeConverter`](src/md_generator/media/youtube/converter.py) reads a `.url` / `.yturl` / `.youtube` file (or a `.txt` whose first non-comment line is a YouTube URL) and implements [`DocumentConverter`](src/md_generator/media/document_converter.py).
|
|
519
|
+
|
|
486
520
|
Public symbols are also re-exported from [`md_generator.media`](src/md_generator/media/__init__.py) for ffprobe helpers (`ffprobe_json`, `VideoProbeResult`, …).
|
|
487
521
|
|
|
488
522
|
### REST API (FastAPI)
|
|
@@ -497,13 +531,15 @@ Each service exposes the same job pattern as other converters:
|
|
|
497
531
|
| `GET /convert/jobs/{job_id}/download` | Markdown when `done`; workspace removed after download. |
|
|
498
532
|
|
|
499
533
|
**Audio** defaults: `MD_AUDIO_MAX_UPLOAD_MB=200`, `MD_AUDIO_MAX_SYNC_UPLOAD_MB=40`, `MD_AUDIO_API_PORT=8011`.
|
|
500
|
-
**Video** defaults: `MD_VIDEO_MAX_UPLOAD_MB=500`, `MD_VIDEO_MAX_SYNC_UPLOAD_MB=80`, `MD_VIDEO_API_PORT=8012`.
|
|
534
|
+
**Video** defaults: `MD_VIDEO_MAX_UPLOAD_MB=500`, `MD_VIDEO_MAX_SYNC_UPLOAD_MB=80`, `MD_VIDEO_API_PORT=8012`.
|
|
535
|
+
**YouTube** uses **JSON** (not multipart): `POST /convert/sync` and `POST /convert/jobs` accept `{"url":"https://www.youtube.com/watch?v=…","title":null,"transcript_languages":["en"],"enable_audio_fallback":true,"whisper_model":"base","language":null}`. Defaults: `MD_YOUTUBE_API_PORT=8013`, `MD_YOUTUBE_JOB_TTL_SECONDS`, `MD_YOUTUBE_CORS_ORIGINS`, `MD_YOUTUBE_TEMP_DIR`.
|
|
501
536
|
|
|
502
537
|
Run with the bundled runners (each call builds the app with **`factory=True`** for a clean MCP session manager):
|
|
503
538
|
|
|
504
539
|
```bash
|
|
505
540
|
md-audio-api --host 127.0.0.1 --port 8011
|
|
506
541
|
md-video-api --host 127.0.0.1 --port 8012
|
|
542
|
+
md-youtube-api --host 127.0.0.1 --port 8013
|
|
507
543
|
```
|
|
508
544
|
|
|
509
545
|
Or with Uvicorn directly (the ASGI app is built by **`create_app()`** so each worker gets its own MCP session manager):
|
|
@@ -511,6 +547,7 @@ Or with Uvicorn directly (the ASGI app is built by **`create_app()`** so each wo
|
|
|
511
547
|
```bash
|
|
512
548
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
513
549
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
550
|
+
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
514
551
|
```
|
|
515
552
|
|
|
516
553
|
The module also defines **`app = create_app()`** for a single-process target: `uvicorn md_generator.media.audio.api.main:app` (no `--factory`).
|
|
@@ -519,7 +556,7 @@ Swagger is at **`/docs`** when the app is running.
|
|
|
519
556
|
|
|
520
557
|
### MCP (stdio, SSE, streamable HTTP)
|
|
521
558
|
|
|
522
|
-
1. **With FastAPI** — start `md-audio-api` or `md-
|
|
559
|
+
1. **With FastAPI** — start `md-audio-api`, `md-video-api`, or `md-youtube-api`; mount Streamable HTTP MCP at **`http://<host>:<port>/mcp`** (same host as REST).
|
|
523
560
|
2. **Standalone** — process speaks MCP only:
|
|
524
561
|
|
|
525
562
|
```bash
|
|
@@ -527,16 +564,18 @@ md-audio-mcp --transport stdio
|
|
|
527
564
|
md-audio-mcp --transport sse
|
|
528
565
|
md-audio-mcp --transport streamable-http
|
|
529
566
|
md-video-mcp --transport stdio
|
|
567
|
+
md-youtube-mcp --transport stdio
|
|
530
568
|
```
|
|
531
569
|
|
|
532
570
|
**Audio MCP tools:** `transcribe_audio_path`, `transcribe_audio_base64`.
|
|
533
|
-
**Video MCP tools:** `transcribe_video_path`, `transcribe_video_base64`.
|
|
571
|
+
**Video MCP tools:** `transcribe_video_path`, `transcribe_video_base64`.
|
|
572
|
+
**YouTube MCP tool:** `youtube_url_to_markdown`.
|
|
534
573
|
|
|
535
|
-
Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python -m md_generator.media.video.api.mcp_server`.
|
|
574
|
+
Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python -m md_generator.media.video.api.mcp_server`, `python -m md_generator.media.youtube.api.mcp_server`.
|
|
536
575
|
|
|
537
576
|
### Thin shims (repo clone)
|
|
538
577
|
|
|
539
|
-
[`audio-to-md/converter.py`](audio-to-md/converter.py)
|
|
578
|
+
[`audio-to-md/converter.py`](audio-to-md/converter.py), [`video-to-md/converter.py`](video-to-md/converter.py), and [`youtube-to-md/converter.py`](youtube-to-md/converter.py) delegate to the same `main` as `md-audio` / `md-video` / `md-youtube`. Tests and `pytest.ini` live under `audio-to-md/tests/`, `video-to-md/tests/`, and `youtube-to-md/tests/`.
|
|
540
579
|
|
|
541
580
|
---
|
|
542
581
|
|
|
@@ -565,8 +604,10 @@ Install `mdengine[api]` plus the format extra(s), then run the **`app`** object
|
|
|
565
604
|
| Text/JSON/XML | `md_generator.text.api.main:app` | `text`, `api`, `mcp` |
|
|
566
605
|
| ZIP | `md_generator.archive.api.main:app` | `archive`, `api`, `mcp` (+ extras for nested office/PDF) |
|
|
567
606
|
| URL / HTML | `md_generator.url.api.main:app` | `url`, `api`, `mcp` |
|
|
607
|
+
| Playwright / SPA | `md_generator.playwright.api.main:app` | `playwright`, `api`, `mcp` |
|
|
568
608
|
| Audio (Whisper) | `md_generator.media.audio.api.main:create_app` (use **`--factory`**) or `…main:app` | `audio`, `api`, `mcp` |
|
|
569
609
|
| Video (Whisper) | `md_generator.media.video.api.main:create_app` (use **`--factory`**) or `…main:app` | `video`, `api`, `mcp` |
|
|
610
|
+
| YouTube | `md_generator.media.youtube.api.main:create_app` (use **`--factory`**) or `…main:app` | `youtube`, `api`, `mcp` |
|
|
570
611
|
|
|
571
612
|
Examples:
|
|
572
613
|
|
|
@@ -575,8 +616,10 @@ uvicorn md_generator.pdf.api.main:app --host 127.0.0.1 --port 8001
|
|
|
575
616
|
uvicorn md_generator.word.api.main:app --host 127.0.0.1 --port 8002
|
|
576
617
|
uvicorn md_generator.archive.api.main:app --host 127.0.0.1 --port 8010
|
|
577
618
|
uvicorn md_generator.url.api.main:app --host 127.0.0.1 --port 8011
|
|
619
|
+
uvicorn md_generator.playwright.api.main:app --host 127.0.0.1 --port 8014
|
|
578
620
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
579
621
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
622
|
+
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
580
623
|
```
|
|
581
624
|
|
|
582
625
|
### MCP over HTTP on the same server
|
|
@@ -596,8 +639,10 @@ Prefixes differ per service (often read from a `.env` file next to the process):
|
|
|
596
639
|
| Text | `TXT_JSON_XML_TO_MD_` | same pattern |
|
|
597
640
|
| XLSX | `XLSX_TO_MD_` | `XLSX_TO_MD_TEMP_DIR`, `XLSX_TO_MD_CORS_ORIGINS`, etc. (see `md_generator.xlsx.api.app`) |
|
|
598
641
|
| URL | `URL_TO_MD_` | `URL_TO_MD_MAX_SYNC_URLS`, `URL_TO_MD_MAX_SYNC_CRAWL_PAGES`, `URL_TO_MD_MAX_JOB_URLS`, `URL_TO_MD_JOB_TTL_SECONDS`, `URL_TO_MD_TEMP_DIR`, `URL_TO_MD_CORS_ORIGINS` |
|
|
642
|
+
| Playwright / SPA | `PLAYWRIGHT_TO_MD_` | `PLAYWRIGHT_TO_MD_MAX_SYNC_URLS`, `PLAYWRIGHT_TO_MD_MAX_JOB_URLS`, `PLAYWRIGHT_TO_MD_JOB_TTL_SECONDS`, `PLAYWRIGHT_TO_MD_TEMP_DIR`, `PLAYWRIGHT_TO_MD_CORS_ORIGINS`, `PLAYWRIGHT_TO_MD_API_HOST`, `PLAYWRIGHT_TO_MD_API_PORT` (default **8014**) |
|
|
599
643
|
| Audio API | `MD_AUDIO_` | `MD_AUDIO_MAX_UPLOAD_MB`, `MD_AUDIO_MAX_SYNC_UPLOAD_MB`, `MD_AUDIO_JOB_TTL_SECONDS`, `MD_AUDIO_TEMP_DIR`, `MD_AUDIO_CORS_ORIGINS`, `MD_AUDIO_API_HOST`, `MD_AUDIO_API_PORT` |
|
|
600
644
|
| Video API | `MD_VIDEO_` | Same pattern as audio with `MD_VIDEO_*` (defaults: larger upload/sync caps, port **8012**) |
|
|
645
|
+
| YouTube API | `MD_YOUTUBE_` | `MD_YOUTUBE_JOB_TTL_SECONDS`, `MD_YOUTUBE_TEMP_DIR`, `MD_YOUTUBE_CORS_ORIGINS`, `MD_YOUTUBE_API_HOST`, `MD_YOUTUBE_API_PORT` (default **8013**); optional `MD_YOUTUBE_YTDLP` path for audio fallback |
|
|
601
646
|
|
|
602
647
|
Exact variable names match the `ApiSettings` / helper functions in each `api/settings` or `api/app` module.
|
|
603
648
|
|
|
@@ -621,8 +666,10 @@ Two usage patterns:
|
|
|
621
666
|
| PPTX | `python -m md_generator.ppt.api.mcp_server` (see module docstring for flags) |
|
|
622
667
|
| Image | `python -m md_generator.image.api.mcp_server` (see module for CLI) |
|
|
623
668
|
| URL / HTML | `python -m md_generator.url.api.mcp_server` / `--transport sse` / `--transport streamable-http` |
|
|
669
|
+
| Playwright / SPA | `md-playwright-mcp` or `python -m md_generator.playwright.api.mcp_server` / `--transport sse` / `--transport streamable-http` |
|
|
624
670
|
| Audio | `md-audio-mcp` or `python -m md_generator.media.audio.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
625
671
|
| Video | `md-video-mcp` or `python -m md_generator.media.video.api.mcp_server` — same transports |
|
|
672
|
+
| YouTube | `md-youtube-mcp` or `python -m md_generator.media.youtube.api.mcp_server` — same transports |
|
|
626
673
|
|
|
627
674
|
**Word** and **XLSX** also ship a small runner script in the repo:
|
|
628
675
|
|
|
@@ -120,10 +120,13 @@ If the shell reports “command not found”, ensure the Python **Scripts** dire
|
|
|
120
120
|
| `md-url` | `md_generator.url.converter:main` | `md-url https://example.com/doc ./web-out --artifact-layout` |
|
|
121
121
|
| `md-audio` | `md_generator.media.audio.converter:main` | `md-audio clip.mp3 transcript.md --model base` |
|
|
122
122
|
| `md-video` | `md_generator.media.video.converter:main` | `md-video clip.mp4 transcript.md --model base` |
|
|
123
|
+
| `md-youtube` | `md_generator.media.youtube.converter:main` | `md-youtube "https://youtu.be/…" out.md --transcript-lang en` |
|
|
123
124
|
| `md-audio-api` | `md_generator.media.audio.api.run:main` | REST + MCP on port **8011** (see [Audio and video to Markdown](#audio-and-video-to-markdown)) |
|
|
124
125
|
| `md-video-api` | `md_generator.media.video.api.run:main` | REST + MCP on port **8012** |
|
|
126
|
+
| `md-youtube-api` | `md_generator.media.youtube.api.run:main` | REST + MCP on port **8013** (JSON `url` body; see same section) |
|
|
125
127
|
| `md-audio-mcp` | `md_generator.media.audio.api.mcp_server:main` | Standalone MCP (`--transport stdio` \| `sse` \| `streamable-http`) |
|
|
126
128
|
| `md-video-mcp` | `md_generator.media.video.api.mcp_server:main` | Same for video |
|
|
129
|
+
| `md-youtube-mcp` | `md_generator.media.youtube.api.mcp_server:main` | Same for YouTube (`youtube_url_to_markdown`) |
|
|
127
130
|
|
|
128
131
|
Every command accepts **`-h` / `--help`** for full flags (artifact layout, OCR, ZIP options, etc.).
|
|
129
132
|
|
|
@@ -337,6 +340,7 @@ Library code lives under **`md_generator.media`**: shared probing in [`document_
|
|
|
337
340
|
```bash
|
|
338
341
|
pip install "mdengine[audio,api,mcp]" # audio CLI + HTTP + MCP
|
|
339
342
|
pip install "mdengine[video,api,mcp]" # video CLI + HTTP + MCP (same ML stack as audio)
|
|
343
|
+
pip install "mdengine[youtube,api,mcp]" # YouTube URL → Markdown (captions + metadata; optional Whisper fallback)
|
|
340
344
|
```
|
|
341
345
|
|
|
342
346
|
### Python library
|
|
@@ -365,6 +369,18 @@ md = svc.to_markdown(Path("input.mp4"), title=None)
|
|
|
365
369
|
svc.write_markdown(Path("input.mp4"), Path("out/transcript.md"))
|
|
366
370
|
```
|
|
367
371
|
|
|
372
|
+
**YouTube** — captions API + page metadata (BeautifulSoup / oEmbed); optional **yt-dlp + Whisper** when captions are missing (`mdengine[audio]` and `yt-dlp` on `PATH`, or `MD_YOUTUBE_YTDLP`):
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from md_generator.media.youtube import YouTubeToMarkdownService
|
|
376
|
+
|
|
377
|
+
svc = YouTubeToMarkdownService(whisper_model="base")
|
|
378
|
+
md = svc.to_markdown("https://www.youtube.com/watch?v=VIDEO_ID", transcript_languages=["en"])
|
|
379
|
+
svc.write_markdown("https://youtu.be/VIDEO_ID", Path("out/youtube.md"))
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
For file-based pipelines, [`YouTubeConverter`](src/md_generator/media/youtube/converter.py) reads a `.url` / `.yturl` / `.youtube` file (or a `.txt` whose first non-comment line is a YouTube URL) and implements [`DocumentConverter`](src/md_generator/media/document_converter.py).
|
|
383
|
+
|
|
368
384
|
Public symbols are also re-exported from [`md_generator.media`](src/md_generator/media/__init__.py) for ffprobe helpers (`ffprobe_json`, `VideoProbeResult`, …).
|
|
369
385
|
|
|
370
386
|
### REST API (FastAPI)
|
|
@@ -379,13 +395,15 @@ Each service exposes the same job pattern as other converters:
|
|
|
379
395
|
| `GET /convert/jobs/{job_id}/download` | Markdown when `done`; workspace removed after download. |
|
|
380
396
|
|
|
381
397
|
**Audio** defaults: `MD_AUDIO_MAX_UPLOAD_MB=200`, `MD_AUDIO_MAX_SYNC_UPLOAD_MB=40`, `MD_AUDIO_API_PORT=8011`.
|
|
382
|
-
**Video** defaults: `MD_VIDEO_MAX_UPLOAD_MB=500`, `MD_VIDEO_MAX_SYNC_UPLOAD_MB=80`, `MD_VIDEO_API_PORT=8012`.
|
|
398
|
+
**Video** defaults: `MD_VIDEO_MAX_UPLOAD_MB=500`, `MD_VIDEO_MAX_SYNC_UPLOAD_MB=80`, `MD_VIDEO_API_PORT=8012`.
|
|
399
|
+
**YouTube** uses **JSON** (not multipart): `POST /convert/sync` and `POST /convert/jobs` accept `{"url":"https://www.youtube.com/watch?v=…","title":null,"transcript_languages":["en"],"enable_audio_fallback":true,"whisper_model":"base","language":null}`. Defaults: `MD_YOUTUBE_API_PORT=8013`, `MD_YOUTUBE_JOB_TTL_SECONDS`, `MD_YOUTUBE_CORS_ORIGINS`, `MD_YOUTUBE_TEMP_DIR`.
|
|
383
400
|
|
|
384
401
|
Run with the bundled runners (each call builds the app with **`factory=True`** for a clean MCP session manager):
|
|
385
402
|
|
|
386
403
|
```bash
|
|
387
404
|
md-audio-api --host 127.0.0.1 --port 8011
|
|
388
405
|
md-video-api --host 127.0.0.1 --port 8012
|
|
406
|
+
md-youtube-api --host 127.0.0.1 --port 8013
|
|
389
407
|
```
|
|
390
408
|
|
|
391
409
|
Or with Uvicorn directly (the ASGI app is built by **`create_app()`** so each worker gets its own MCP session manager):
|
|
@@ -393,6 +411,7 @@ Or with Uvicorn directly (the ASGI app is built by **`create_app()`** so each wo
|
|
|
393
411
|
```bash
|
|
394
412
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
395
413
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
414
|
+
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
396
415
|
```
|
|
397
416
|
|
|
398
417
|
The module also defines **`app = create_app()`** for a single-process target: `uvicorn md_generator.media.audio.api.main:app` (no `--factory`).
|
|
@@ -401,7 +420,7 @@ Swagger is at **`/docs`** when the app is running.
|
|
|
401
420
|
|
|
402
421
|
### MCP (stdio, SSE, streamable HTTP)
|
|
403
422
|
|
|
404
|
-
1. **With FastAPI** — start `md-audio-api` or `md-
|
|
423
|
+
1. **With FastAPI** — start `md-audio-api`, `md-video-api`, or `md-youtube-api`; mount Streamable HTTP MCP at **`http://<host>:<port>/mcp`** (same host as REST).
|
|
405
424
|
2. **Standalone** — process speaks MCP only:
|
|
406
425
|
|
|
407
426
|
```bash
|
|
@@ -409,16 +428,18 @@ md-audio-mcp --transport stdio
|
|
|
409
428
|
md-audio-mcp --transport sse
|
|
410
429
|
md-audio-mcp --transport streamable-http
|
|
411
430
|
md-video-mcp --transport stdio
|
|
431
|
+
md-youtube-mcp --transport stdio
|
|
412
432
|
```
|
|
413
433
|
|
|
414
434
|
**Audio MCP tools:** `transcribe_audio_path`, `transcribe_audio_base64`.
|
|
415
|
-
**Video MCP tools:** `transcribe_video_path`, `transcribe_video_base64`.
|
|
435
|
+
**Video MCP tools:** `transcribe_video_path`, `transcribe_video_base64`.
|
|
436
|
+
**YouTube MCP tool:** `youtube_url_to_markdown`.
|
|
416
437
|
|
|
417
|
-
Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python -m md_generator.media.video.api.mcp_server`.
|
|
438
|
+
Equivalent modules: `python -m md_generator.media.audio.api.mcp_server`, `python -m md_generator.media.video.api.mcp_server`, `python -m md_generator.media.youtube.api.mcp_server`.
|
|
418
439
|
|
|
419
440
|
### Thin shims (repo clone)
|
|
420
441
|
|
|
421
|
-
[`audio-to-md/converter.py`](audio-to-md/converter.py)
|
|
442
|
+
[`audio-to-md/converter.py`](audio-to-md/converter.py), [`video-to-md/converter.py`](video-to-md/converter.py), and [`youtube-to-md/converter.py`](youtube-to-md/converter.py) delegate to the same `main` as `md-audio` / `md-video` / `md-youtube`. Tests and `pytest.ini` live under `audio-to-md/tests/`, `video-to-md/tests/`, and `youtube-to-md/tests/`.
|
|
422
443
|
|
|
423
444
|
---
|
|
424
445
|
|
|
@@ -447,8 +468,10 @@ Install `mdengine[api]` plus the format extra(s), then run the **`app`** object
|
|
|
447
468
|
| Text/JSON/XML | `md_generator.text.api.main:app` | `text`, `api`, `mcp` |
|
|
448
469
|
| ZIP | `md_generator.archive.api.main:app` | `archive`, `api`, `mcp` (+ extras for nested office/PDF) |
|
|
449
470
|
| URL / HTML | `md_generator.url.api.main:app` | `url`, `api`, `mcp` |
|
|
471
|
+
| Playwright / SPA | `md_generator.playwright.api.main:app` | `playwright`, `api`, `mcp` |
|
|
450
472
|
| Audio (Whisper) | `md_generator.media.audio.api.main:create_app` (use **`--factory`**) or `…main:app` | `audio`, `api`, `mcp` |
|
|
451
473
|
| Video (Whisper) | `md_generator.media.video.api.main:create_app` (use **`--factory`**) or `…main:app` | `video`, `api`, `mcp` |
|
|
474
|
+
| YouTube | `md_generator.media.youtube.api.main:create_app` (use **`--factory`**) or `…main:app` | `youtube`, `api`, `mcp` |
|
|
452
475
|
|
|
453
476
|
Examples:
|
|
454
477
|
|
|
@@ -457,8 +480,10 @@ uvicorn md_generator.pdf.api.main:app --host 127.0.0.1 --port 8001
|
|
|
457
480
|
uvicorn md_generator.word.api.main:app --host 127.0.0.1 --port 8002
|
|
458
481
|
uvicorn md_generator.archive.api.main:app --host 127.0.0.1 --port 8010
|
|
459
482
|
uvicorn md_generator.url.api.main:app --host 127.0.0.1 --port 8011
|
|
483
|
+
uvicorn md_generator.playwright.api.main:app --host 127.0.0.1 --port 8014
|
|
460
484
|
uvicorn md_generator.media.audio.api.main:create_app --factory --host 127.0.0.1 --port 8011
|
|
461
485
|
uvicorn md_generator.media.video.api.main:create_app --factory --host 127.0.0.1 --port 8012
|
|
486
|
+
uvicorn md_generator.media.youtube.api.main:create_app --factory --host 127.0.0.1 --port 8013
|
|
462
487
|
```
|
|
463
488
|
|
|
464
489
|
### MCP over HTTP on the same server
|
|
@@ -478,8 +503,10 @@ Prefixes differ per service (often read from a `.env` file next to the process):
|
|
|
478
503
|
| Text | `TXT_JSON_XML_TO_MD_` | same pattern |
|
|
479
504
|
| XLSX | `XLSX_TO_MD_` | `XLSX_TO_MD_TEMP_DIR`, `XLSX_TO_MD_CORS_ORIGINS`, etc. (see `md_generator.xlsx.api.app`) |
|
|
480
505
|
| URL | `URL_TO_MD_` | `URL_TO_MD_MAX_SYNC_URLS`, `URL_TO_MD_MAX_SYNC_CRAWL_PAGES`, `URL_TO_MD_MAX_JOB_URLS`, `URL_TO_MD_JOB_TTL_SECONDS`, `URL_TO_MD_TEMP_DIR`, `URL_TO_MD_CORS_ORIGINS` |
|
|
506
|
+
| Playwright / SPA | `PLAYWRIGHT_TO_MD_` | `PLAYWRIGHT_TO_MD_MAX_SYNC_URLS`, `PLAYWRIGHT_TO_MD_MAX_JOB_URLS`, `PLAYWRIGHT_TO_MD_JOB_TTL_SECONDS`, `PLAYWRIGHT_TO_MD_TEMP_DIR`, `PLAYWRIGHT_TO_MD_CORS_ORIGINS`, `PLAYWRIGHT_TO_MD_API_HOST`, `PLAYWRIGHT_TO_MD_API_PORT` (default **8014**) |
|
|
481
507
|
| Audio API | `MD_AUDIO_` | `MD_AUDIO_MAX_UPLOAD_MB`, `MD_AUDIO_MAX_SYNC_UPLOAD_MB`, `MD_AUDIO_JOB_TTL_SECONDS`, `MD_AUDIO_TEMP_DIR`, `MD_AUDIO_CORS_ORIGINS`, `MD_AUDIO_API_HOST`, `MD_AUDIO_API_PORT` |
|
|
482
508
|
| Video API | `MD_VIDEO_` | Same pattern as audio with `MD_VIDEO_*` (defaults: larger upload/sync caps, port **8012**) |
|
|
509
|
+
| YouTube API | `MD_YOUTUBE_` | `MD_YOUTUBE_JOB_TTL_SECONDS`, `MD_YOUTUBE_TEMP_DIR`, `MD_YOUTUBE_CORS_ORIGINS`, `MD_YOUTUBE_API_HOST`, `MD_YOUTUBE_API_PORT` (default **8013**); optional `MD_YOUTUBE_YTDLP` path for audio fallback |
|
|
483
510
|
|
|
484
511
|
Exact variable names match the `ApiSettings` / helper functions in each `api/settings` or `api/app` module.
|
|
485
512
|
|
|
@@ -503,8 +530,10 @@ Two usage patterns:
|
|
|
503
530
|
| PPTX | `python -m md_generator.ppt.api.mcp_server` (see module docstring for flags) |
|
|
504
531
|
| Image | `python -m md_generator.image.api.mcp_server` (see module for CLI) |
|
|
505
532
|
| URL / HTML | `python -m md_generator.url.api.mcp_server` / `--transport sse` / `--transport streamable-http` |
|
|
533
|
+
| Playwright / SPA | `md-playwright-mcp` or `python -m md_generator.playwright.api.mcp_server` / `--transport sse` / `--transport streamable-http` |
|
|
506
534
|
| Audio | `md-audio-mcp` or `python -m md_generator.media.audio.api.mcp_server` — `--transport stdio` (default), `sse`, `streamable-http` |
|
|
507
535
|
| Video | `md-video-mcp` or `python -m md_generator.media.video.api.mcp_server` — same transports |
|
|
536
|
+
| YouTube | `md-youtube-mcp` or `python -m md_generator.media.youtube.api.mcp_server` — same transports |
|
|
508
537
|
|
|
509
538
|
**Word** and **XLSX** also ship a small runner script in the repo:
|
|
510
539
|
|
|
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mdengine"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Convert PDF, Office, images, audio, video, text/JSON/XML, ZIP archives, and web URLs to Markdown."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [{ name = "mdengine contributors" }]
|
|
14
|
-
keywords = ["markdown", "pdf", "docx", "pptx", "xlsx", "ocr", "zip", "url", "html"]
|
|
14
|
+
keywords = ["markdown", "pdf", "docx", "pptx", "xlsx", "ocr", "zip", "url", "html", "youtube", "playwright"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -76,6 +76,22 @@ url-full = [
|
|
|
76
76
|
audio = ["openai-whisper>=20231117", "imageio-ffmpeg>=0.5.1"]
|
|
77
77
|
# Same ML stack as audio; ships a bundled ``ffmpeg`` binary when the system has none.
|
|
78
78
|
video = ["openai-whisper>=20231117", "imageio-ffmpeg>=0.5.1"]
|
|
79
|
+
youtube = [
|
|
80
|
+
"youtube-transcript-api>=0.6.0",
|
|
81
|
+
"httpx>=0.27.0",
|
|
82
|
+
"beautifulsoup4>=4.12.0",
|
|
83
|
+
"lxml>=5.0.0",
|
|
84
|
+
]
|
|
85
|
+
# Playwright-rendered SPA → Markdown (install browsers: playwright install chromium).
|
|
86
|
+
playwright = [
|
|
87
|
+
"playwright>=1.49.0",
|
|
88
|
+
"httpx>=0.27.0",
|
|
89
|
+
"beautifulsoup4>=4.12.0",
|
|
90
|
+
"lxml>=5.0.0",
|
|
91
|
+
"markdownify>=0.14.0",
|
|
92
|
+
"readability-lxml>=0.8.1",
|
|
93
|
+
"Pillow>=10.0.0",
|
|
94
|
+
]
|
|
79
95
|
api = [
|
|
80
96
|
"fastapi>=0.115.0",
|
|
81
97
|
"uvicorn[standard]>=0.32.0",
|
|
@@ -87,9 +103,13 @@ mcp = ["mcp>=1.2.0", "fastmcp>=2.3.0"]
|
|
|
87
103
|
dev = [
|
|
88
104
|
"pytest>=8.0.0",
|
|
89
105
|
"httpx>=0.27.0",
|
|
106
|
+
"beautifulsoup4>=4.12.0",
|
|
107
|
+
"lxml>=5.0.0",
|
|
108
|
+
"youtube-transcript-api>=0.6.0",
|
|
90
109
|
"mdengine[api]",
|
|
91
110
|
"mdengine[mcp]",
|
|
92
111
|
"mdengine[url]",
|
|
112
|
+
"mdengine[playwright]",
|
|
93
113
|
]
|
|
94
114
|
all = [
|
|
95
115
|
"pymupdf>=1.24.0",
|
|
@@ -116,6 +136,7 @@ all = [
|
|
|
116
136
|
"beautifulsoup4>=4.12.0",
|
|
117
137
|
"lxml_html_clean>=0.4.0",
|
|
118
138
|
"readability-lxml>=0.8.1",
|
|
139
|
+
"youtube-transcript-api>=0.6.0",
|
|
119
140
|
]
|
|
120
141
|
|
|
121
142
|
[project.urls]
|
|
@@ -138,6 +159,12 @@ md-audio-api = "md_generator.media.audio.api.run:main"
|
|
|
138
159
|
md-video-api = "md_generator.media.video.api.run:main"
|
|
139
160
|
md-audio-mcp = "md_generator.media.audio.api.mcp_server:main"
|
|
140
161
|
md-video-mcp = "md_generator.media.video.api.mcp_server:main"
|
|
162
|
+
md-youtube = "md_generator.media.youtube.converter:main"
|
|
163
|
+
md-youtube-api = "md_generator.media.youtube.api.run:main"
|
|
164
|
+
md-youtube-mcp = "md_generator.media.youtube.api.mcp_server:main"
|
|
165
|
+
md-playwright = "md_generator.playwright.cli:main"
|
|
166
|
+
md-playwright-api = "md_generator.playwright.api.run:main"
|
|
167
|
+
md-playwright-mcp = "md_generator.playwright.api.mcp_server:main"
|
|
141
168
|
|
|
142
169
|
[tool.setuptools.packages.find]
|
|
143
170
|
where = ["src"]
|
|
@@ -155,5 +182,7 @@ testpaths = [
|
|
|
155
182
|
"url-to-md/tests",
|
|
156
183
|
"audio-to-md/tests",
|
|
157
184
|
"video-to-md/tests",
|
|
185
|
+
"youtube-to-md/tests",
|
|
186
|
+
"playwright-to-md/tests",
|
|
158
187
|
]
|
|
159
188
|
markers = ["integration: optional dependency or slower checks"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""YouTube URL → Markdown (metadata, description, timestamped transcript)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from md_generator.media.youtube.converter import YouTubeConverter
|
|
6
|
+
from md_generator.media.youtube.formatter import YouTubeMarkdownFormatter
|
|
7
|
+
from md_generator.media.youtube.metadata import (
|
|
8
|
+
YouTubeMetadataError,
|
|
9
|
+
extract_video_id,
|
|
10
|
+
fetch_youtube_metadata,
|
|
11
|
+
normalize_youtube_url,
|
|
12
|
+
)
|
|
13
|
+
from md_generator.media.youtube.service import (
|
|
14
|
+
YouTubeConversionResult,
|
|
15
|
+
YouTubeError,
|
|
16
|
+
YouTubeToMarkdownService,
|
|
17
|
+
read_youtube_url_from_path,
|
|
18
|
+
)
|
|
19
|
+
from md_generator.media.youtube.transcript import YouTubeTranscriptError, fetch_transcript
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"YouTubeConversionResult",
|
|
23
|
+
"YouTubeConverter",
|
|
24
|
+
"YouTubeError",
|
|
25
|
+
"YouTubeMarkdownFormatter",
|
|
26
|
+
"YouTubeMetadataError",
|
|
27
|
+
"YouTubeToMarkdownService",
|
|
28
|
+
"YouTubeTranscriptError",
|
|
29
|
+
"extract_video_id",
|
|
30
|
+
"fetch_transcript",
|
|
31
|
+
"fetch_youtube_metadata",
|
|
32
|
+
"normalize_youtube_url",
|
|
33
|
+
"read_youtube_url_from_path",
|
|
34
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""YouTube REST API and MCP entrypoints."""
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""FastAPI: JSON URL conversion (sync + jobs) and Streamable HTTP MCP at ``/mcp``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import tempfile
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
10
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
11
|
+
from fastapi.responses import FileResponse, Response
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
from starlette.background import BackgroundTask
|
|
14
|
+
|
|
15
|
+
from md_generator.media.job_store import MediaJobStore
|
|
16
|
+
from md_generator.media.youtube.api.mcp_setup import build_mcp_stack
|
|
17
|
+
from md_generator.media.youtube.api.settings import YouTubeApiSettings, cors_list
|
|
18
|
+
from md_generator.media.youtube.metadata import extract_video_id
|
|
19
|
+
from md_generator.media.youtube.service import YouTubeError, YouTubeToMarkdownService
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class YouTubeConvertRequest(BaseModel):
|
|
23
|
+
url: str = Field(..., min_length=1, description="YouTube watch, youtu.be, shorts, or embed URL")
|
|
24
|
+
title: str | None = None
|
|
25
|
+
transcript_languages: list[str] | None = None
|
|
26
|
+
enable_audio_fallback: bool = True
|
|
27
|
+
whisper_model: str = "base"
|
|
28
|
+
language: str | None = Field(default=None, description="Whisper language if audio fallback runs")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _validate_url(url: str) -> str:
|
|
32
|
+
u = url.strip()
|
|
33
|
+
if extract_video_id(u) is None:
|
|
34
|
+
raise HTTPException(status_code=400, detail="Invalid or unsupported YouTube URL")
|
|
35
|
+
return u
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_app() -> FastAPI:
|
|
39
|
+
mcp, mcp_http = build_mcp_stack(mount_under_fastapi=True)
|
|
40
|
+
|
|
41
|
+
@asynccontextmanager
|
|
42
|
+
async def lifespan(application: FastAPI):
|
|
43
|
+
settings = YouTubeApiSettings()
|
|
44
|
+
base = Path(settings.temp_dir) if settings.temp_dir else None
|
|
45
|
+
store = MediaJobStore(base, settings.job_ttl_seconds)
|
|
46
|
+
store.start_sweeper()
|
|
47
|
+
application.state.settings = settings
|
|
48
|
+
application.state.job_store = store
|
|
49
|
+
async with mcp.session_manager.run():
|
|
50
|
+
yield
|
|
51
|
+
|
|
52
|
+
app = FastAPI(title="youtube-to-md", lifespan=lifespan)
|
|
53
|
+
app.mount("/mcp", mcp_http)
|
|
54
|
+
|
|
55
|
+
_bootstrap = YouTubeApiSettings()
|
|
56
|
+
app.add_middleware(
|
|
57
|
+
CORSMiddleware,
|
|
58
|
+
allow_origins=cors_list(_bootstrap),
|
|
59
|
+
allow_credentials="*" not in cors_list(_bootstrap),
|
|
60
|
+
allow_methods=["*"],
|
|
61
|
+
allow_headers=["*"],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@app.post("/convert/sync")
|
|
65
|
+
async def convert_sync(body: YouTubeConvertRequest) -> Response:
|
|
66
|
+
url = _validate_url(body.url)
|
|
67
|
+
with tempfile.TemporaryDirectory(prefix="yt-sync-") as td_name:
|
|
68
|
+
out = Path(td_name) / "transcript.md"
|
|
69
|
+
svc = YouTubeToMarkdownService(whisper_model=body.whisper_model, whisper_language=body.language)
|
|
70
|
+
try:
|
|
71
|
+
svc.write_markdown(
|
|
72
|
+
url,
|
|
73
|
+
out,
|
|
74
|
+
title=body.title,
|
|
75
|
+
transcript_languages=body.transcript_languages,
|
|
76
|
+
enable_audio_fallback=body.enable_audio_fallback,
|
|
77
|
+
)
|
|
78
|
+
except YouTubeError as e:
|
|
79
|
+
raise HTTPException(status_code=422, detail=str(e)) from e
|
|
80
|
+
md = out.read_text(encoding="utf-8")
|
|
81
|
+
return Response(
|
|
82
|
+
content=md.encode("utf-8"),
|
|
83
|
+
media_type="text/markdown; charset=utf-8",
|
|
84
|
+
headers={"Content-Disposition": 'attachment; filename="transcript.md"'},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@app.post("/convert/jobs")
|
|
88
|
+
async def convert_jobs(request: Request, body: YouTubeConvertRequest) -> dict:
|
|
89
|
+
url = _validate_url(body.url)
|
|
90
|
+
store: MediaJobStore = request.app.state.job_store
|
|
91
|
+
job = store.create_job()
|
|
92
|
+
out = job.workspace / "transcript.md"
|
|
93
|
+
|
|
94
|
+
def work() -> None:
|
|
95
|
+
svc = YouTubeToMarkdownService(whisper_model=body.whisper_model, whisper_language=body.language)
|
|
96
|
+
svc.write_markdown(
|
|
97
|
+
url,
|
|
98
|
+
out,
|
|
99
|
+
title=body.title,
|
|
100
|
+
transcript_languages=body.transcript_languages,
|
|
101
|
+
enable_audio_fallback=body.enable_audio_fallback,
|
|
102
|
+
)
|
|
103
|
+
job.result_path = out
|
|
104
|
+
|
|
105
|
+
store.run_async(job, work)
|
|
106
|
+
return {"job_id": job.job_id, "status": job.status}
|
|
107
|
+
|
|
108
|
+
@app.get("/convert/jobs/{job_id}")
|
|
109
|
+
async def job_status(request: Request, job_id: str) -> dict:
|
|
110
|
+
store: MediaJobStore = request.app.state.job_store
|
|
111
|
+
job = store.get(job_id)
|
|
112
|
+
if not job:
|
|
113
|
+
raise HTTPException(404, detail="Unknown job_id")
|
|
114
|
+
return {
|
|
115
|
+
"status": job.status,
|
|
116
|
+
"error": job.error,
|
|
117
|
+
"created_at": job.created_at,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@app.get("/convert/jobs/{job_id}/download", response_class=FileResponse)
|
|
121
|
+
async def job_download(request: Request, job_id: str) -> FileResponse:
|
|
122
|
+
store: MediaJobStore = request.app.state.job_store
|
|
123
|
+
job = store.get(job_id)
|
|
124
|
+
if not job:
|
|
125
|
+
raise HTTPException(404, detail="Unknown job_id")
|
|
126
|
+
if job.status != "done" or not job.result_path or not job.result_path.is_file():
|
|
127
|
+
raise HTTPException(400, detail="Job is not ready for download")
|
|
128
|
+
path = job.result_path
|
|
129
|
+
task = BackgroundTask(store.remove_after_download, job)
|
|
130
|
+
return FileResponse(
|
|
131
|
+
path,
|
|
132
|
+
media_type="text/markdown; charset=utf-8",
|
|
133
|
+
filename="transcript.md",
|
|
134
|
+
background=task,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return app
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
app = create_app()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Standalone MCP server: stdio, SSE, or streamable HTTP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
parser = argparse.ArgumentParser(description="youtube-to-md MCP server")
|
|
11
|
+
parser.add_argument(
|
|
12
|
+
"--transport",
|
|
13
|
+
choices=("stdio", "sse", "streamable-http"),
|
|
14
|
+
default="stdio",
|
|
15
|
+
)
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
from md_generator.media.youtube.api.mcp_setup import build_mcp_stack
|
|
19
|
+
|
|
20
|
+
mcp, _ = build_mcp_stack(mount_under_fastapi=False)
|
|
21
|
+
|
|
22
|
+
if args.transport == "stdio":
|
|
23
|
+
asyncio.run(mcp.run_stdio_async())
|
|
24
|
+
elif args.transport == "sse":
|
|
25
|
+
asyncio.run(mcp.run_sse_async())
|
|
26
|
+
else:
|
|
27
|
+
asyncio.run(mcp.run_streamable_http_async())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|