md2video-audio-skill 1.0.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.
@@ -0,0 +1,30 @@
1
+ name: 自动发布到 PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published] # 只有当你在 GitHub 网页上真正点击“发布新版本”时才触发
6
+
7
+ jobs:
8
+ build-n-publish:
9
+ name: 编译并安全上传至 PyPI
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write # 这个核心权限必须开启,用于和 PyPI 官方进行无密钥安全握手
13
+
14
+ steps:
15
+ - name: 拉取代码
16
+ uses: actions/checkout@v4
17
+
18
+ - name: 设置 Python 环境
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.10"
22
+
23
+ - name: 安装现代打包工具
24
+ run: pip install build
25
+
26
+ - name: 编译出静态包
27
+ run: python -m build
28
+
29
+ - name: 通过免密认证发布到 PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1 # 官方受信任的发布插件
@@ -0,0 +1,39 @@
1
+ # =========================
2
+ # macOS 系统自动生成文件
3
+ # =========================
4
+ .DS_Store
5
+ .AppleDouble
6
+ .LSOverride
7
+
8
+ # 缩略图与图标
9
+ Icon?
10
+ ._*
11
+
12
+ # 临时文件与网络卷垃圾桶
13
+ .Trashes
14
+ .Spotlight-V100
15
+ .TemporaryItems
16
+
17
+ # 敏感配置文件
18
+ .env
19
+
20
+ # =========================
21
+ # Python 编译、打包与虚拟环境缓存(新增)
22
+ # =========================
23
+ # 过滤本地运行 python -m build 生成的发布压缩包目录
24
+ dist/
25
+ build/
26
+
27
+ # 过滤 Python 自动生成的元数据描述文件夹
28
+ *.egg-info/
29
+ .eggs/
30
+
31
+ # 过滤 Python 运行时自动生成的字节码缓存(杜绝 __pycache__ 污染)
32
+ __pycache__/
33
+ *.py[cod]
34
+ *$py.class
35
+
36
+ # 过滤你本地可能用到的 Python 虚拟环境目录
37
+ .venv/
38
+ venv/
39
+ env/
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.5
2
+ Name: md2video-audio-skill
3
+ Version: 1.0.0
4
+ Summary: One Markdown file + a one-click command = professional-grade talking-head videos. | 一个 Markdown 文件 + 一键命令 = 专业级口播视频。
5
+ Project-URL: Homepage, https://github.com/70v-Yoyo/md2video-audio-skill
6
+ Project-URL: Bug-Tracker, https://github.com/70v-Yoyo/md2video-audio-skill
7
+ Project-URL: Repository, https://github.com/70v-Yoyo/md2video-audio-skill
8
+ Author: 70v-Yoyo
9
+ License: Apache-2.0
10
+ License-File: LICENSE
11
+ Keywords: ai-agent,claude-code,markdown-to-video,mcp,text-to-speech,video-generation
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Multimedia :: Video :: Conversion
17
+ Requires-Python: >=3.8
18
+ Description-Content-Type: text/markdown
19
+
20
+ [![Downloads](https://static.pepy.tech/badge/md2video-audio-skill)](https://pepy.tech/projects/md2video-audio-skill) [![README Views](https://visitor-badge.laobi.icu/badge?page_id=70v-Yoyo.md2video-audio-skill&left_text=README%20Views)](https://github.com/70v-Yoyo/md2video-audio-skill) [![License](https://img.shields.io/github/license/70v-Yoyo/md2video-audio-skill?style=flat-square)](https://github.com/70v-Yoyo/md2video-audio-skill/blob/main/LICENSE)
21
+
22
+ ---
23
+
24
+ 能被你 `git clone` 或是阅读,是作为作者最开心的事!如果它解决了你的问题,不妨点亮右上角的小星星,不仅防止找不到仓库,还能动态推送追更哦。
25
+
26
+ 💖 **用一个免费的 ⭐ Star 帮我加速吧!Thank You !**
27
+
28
+ ---
29
+
30
+ # md2video-audio-skill
31
+
32
+ One Markdown file + a one-click command = professional-grade talking-head videos. | 一个 Markdown 文件 + 一键命令 = 专业级口播视频。
33
+
34
+ - ✨ `/md2video-audio` 是什么神仙技能?
35
+
36
+ 这是一个**完全免费**的 Markdown 转视频技能,能把你的 `.md` 文件直接编译成带真人配音的 MP4 视频:
37
+
38
+ ```
39
+ Markdown 文档 → Marp 幻灯片 + Edge-TTS 配音 → 成品 MP4
40
+ ```
41
+
42
+ - 🔥 核心亮点
43
+
44
+ | 特性 | 说明 |
45
+ | -------------- | ----------------------------------------- |
46
+ | 💰 **零成本** | 不用 OpenAI、不用阿里云、不用任何付费 API |
47
+ | 🎙️ **真人配音** | 微软 Edge-TTS 音色,自然到以为是真人录的 |
48
+ | 🖼️ **自动配图** | Marp 渲染精美幻灯片作为讲解画面 |
49
+ | ⚡ **一键生成** | 一条命令搞定全部流程 |
50
+ | **标准输出** | 直接产出可上传抖音/B 站/YouTube 的 MP4 |
51
+
52
+ ---
53
+
54
+ ## 🚀 怎么用?超简单!
55
+
56
+ ```bash
57
+ #安装方法
58
+ #1 npx安装
59
+ npx skills add https://www.modelscope.cn/skills/nihaoModelscope/md2video-audio-skill
60
+ #2 SDK安装
61
+ pip install --upgrade modelscope
62
+ modelscope skills add nihaoModelscope/md2video-audio-skill
63
+ #3 bash安装
64
+ curl -fsSL https://www.modelscope.cn/skills/install.sh | bash -s -- nihaoModelscope/md2video-audio-skill
65
+
66
+ #使用方法
67
+ # 在 Claude Code 里输入: 其他agent也可轻松适配
68
+ /md2video-audio your-presentation.md
69
+ ```
70
+
71
+ 就这样。没有复杂配置,没有 API 密钥,没有第三方依赖。
72
+
73
+ ----
74
+
75
+ - 适合什么场景?
76
+
77
+ - **知识分享**:技术教程、读书笔记、概念讲解
78
+ - 💼 **工作汇报**:项目复盘、数据分析、进度同步
79
+ - **在线课程**:课件转视频、微课制作
80
+ - 📢 **营销内容**:产品介绍、功能演示、活动预告
81
+ - **个人 IP**:日更视频、观点输出、经验总结
82
+
83
+ ---
84
+
85
+ - 💡 为什么推荐它?
86
+
87
+ 1. **真的免费** —— 很多类似工具最后都要你买积分、充会员,这个是纯本地方案
88
+ 2. **质量靠谱** —— 目前最自然的中文 TTS 免费配音
89
+ 3. **门槛极低** —— 会写 Markdown 就会用,不用学 Premiere
90
+ 4. **可控性强** —— 文案改 MD 就行,不用重新录制
91
+
92
+ -----
93
+
94
+
95
+
96
+ # md2video-audio-skill- EN
97
+
98
+ One Markdown file + a one-click command = professional-grade talking-head videos.
99
+
100
+ ✨ What kind of magical skill is `/md2video-audio`?
101
+
102
+ This is a completely free Markdown-to-video skill that directly compiles your `.md` files into MP4 videos with realistic human-like voiceovers:
103
+
104
+ Markdown Document → Marp Slides + Edge-TTS Voice → Finished MP4
105
+
106
+ 🔥 Core Highlights
107
+
108
+ | **Feature** | **Description** |
109
+ | ------------------------ | ------------------------------------------------------------ |
110
+ | 💰 Zero Cost | No OpenAI, no Alibaba Cloud, no paid APIs required. |
111
+ | 🎙️ Realistic Voiceover | Powered by Microsoft Edge-TTS voice, so natural it sounds like a real person. |
112
+ | 🖼️ Auto-Generated Visuals | Uses Marp to render stunning slides as presentation visuals. |
113
+ | ⚡ One-Click Generation | Complete the entire workflow with a single command. |
114
+ | 📦 Standard Output | Directly produces MP4 videos ready to upload to TikTok, Bilibili, or YouTube. |
115
+
116
+ 🚀 How to use? Super simple!
117
+
118
+ - Installation Methods
119
+
120
+ 1. npx installation
121
+
122
+ `npx skills add https://www.modelscope.cn/skills/nihaoModelscope/md2video-audio-skill`
123
+
124
+ 2. SDK installation
125
+
126
+ `pip install --upgrade modelscope modelscope skills add nihaoModelscope/md2video-audio-skill`
127
+
128
+ 3. bash installation
129
+
130
+ `curl -fsSL https://www.modelscope.cn/skills/install.sh | bash -s -- nihaoModelscope/md2video-audio-skill`
131
+
132
+ ## Usage
133
+
134
+ - Type in Claude Code (easily adaptable for other agents):
135
+
136
+ `/md2video-audio your-presentation.md`
137
+
138
+ That's it. No complicated setup, no API keys, no third-party dependencies.
139
+
140
+ - Suitable Scenarios?
141
+
142
+ - Knowledge Sharing: Tech tutorials, reading notes, concept explanations
143
+
144
+ - 💼 Work Reports: Project reviews, data analysis, progress updates
145
+
146
+ - Online Courses: Slide-to-video conversion, micro-lesson production
147
+
148
+ - 📢 Marketing Content: Product introductions, feature demos, event announcements
149
+
150
+ - Personal Branding: Daily video updates, opinion publishing, experience summaries
151
+
152
+ - 💡 Why recommend it?
153
+
154
+ - Truly Free: Many similar tools eventually require buying credits or memberships; this is a purely local solution.
155
+
156
+ - Reliable Quality: free and natural TTS options available.
157
+
158
+ - Extremely Low Barrier: If you can write Markdown, you can use it—no Premiere required.
159
+
160
+ - High Controllability: Just edit the Markdown text to make changes, no need to re-record.
161
+
162
+ ---
163
+
164
+ # Attribution
165
+
166
+ Based on [md2video-audio-skill](https://github.com/70v-Yoyo/md2video-audio-skill) by 70v-Yoyo.
167
+
168
+ Derivative works and redistributions must retain attribution to 70v-Yoyo and include the original repository address.
@@ -0,0 +1,149 @@
1
+ [![Downloads](https://static.pepy.tech/badge/md2video-audio-skill)](https://pepy.tech/projects/md2video-audio-skill) [![README Views](https://visitor-badge.laobi.icu/badge?page_id=70v-Yoyo.md2video-audio-skill&left_text=README%20Views)](https://github.com/70v-Yoyo/md2video-audio-skill) [![License](https://img.shields.io/github/license/70v-Yoyo/md2video-audio-skill?style=flat-square)](https://github.com/70v-Yoyo/md2video-audio-skill/blob/main/LICENSE)
2
+
3
+ ---
4
+
5
+ 能被你 `git clone` 或是阅读,是作为作者最开心的事!如果它解决了你的问题,不妨点亮右上角的小星星,不仅防止找不到仓库,还能动态推送追更哦。
6
+
7
+ 💖 **用一个免费的 ⭐ Star 帮我加速吧!Thank You !**
8
+
9
+ ---
10
+
11
+ # md2video-audio-skill
12
+
13
+ One Markdown file + a one-click command = professional-grade talking-head videos. | 一个 Markdown 文件 + 一键命令 = 专业级口播视频。
14
+
15
+ - ✨ `/md2video-audio` 是什么神仙技能?
16
+
17
+ 这是一个**完全免费**的 Markdown 转视频技能,能把你的 `.md` 文件直接编译成带真人配音的 MP4 视频:
18
+
19
+ ```
20
+ Markdown 文档 → Marp 幻灯片 + Edge-TTS 配音 → 成品 MP4
21
+ ```
22
+
23
+ - 🔥 核心亮点
24
+
25
+ | 特性 | 说明 |
26
+ | -------------- | ----------------------------------------- |
27
+ | 💰 **零成本** | 不用 OpenAI、不用阿里云、不用任何付费 API |
28
+ | 🎙️ **真人配音** | 微软 Edge-TTS 音色,自然到以为是真人录的 |
29
+ | 🖼️ **自动配图** | Marp 渲染精美幻灯片作为讲解画面 |
30
+ | ⚡ **一键生成** | 一条命令搞定全部流程 |
31
+ | **标准输出** | 直接产出可上传抖音/B 站/YouTube 的 MP4 |
32
+
33
+ ---
34
+
35
+ ## 🚀 怎么用?超简单!
36
+
37
+ ```bash
38
+ #安装方法
39
+ #1 npx安装
40
+ npx skills add https://www.modelscope.cn/skills/nihaoModelscope/md2video-audio-skill
41
+ #2 SDK安装
42
+ pip install --upgrade modelscope
43
+ modelscope skills add nihaoModelscope/md2video-audio-skill
44
+ #3 bash安装
45
+ curl -fsSL https://www.modelscope.cn/skills/install.sh | bash -s -- nihaoModelscope/md2video-audio-skill
46
+
47
+ #使用方法
48
+ # 在 Claude Code 里输入: 其他agent也可轻松适配
49
+ /md2video-audio your-presentation.md
50
+ ```
51
+
52
+ 就这样。没有复杂配置,没有 API 密钥,没有第三方依赖。
53
+
54
+ ----
55
+
56
+ - 适合什么场景?
57
+
58
+ - **知识分享**:技术教程、读书笔记、概念讲解
59
+ - 💼 **工作汇报**:项目复盘、数据分析、进度同步
60
+ - **在线课程**:课件转视频、微课制作
61
+ - 📢 **营销内容**:产品介绍、功能演示、活动预告
62
+ - **个人 IP**:日更视频、观点输出、经验总结
63
+
64
+ ---
65
+
66
+ - 💡 为什么推荐它?
67
+
68
+ 1. **真的免费** —— 很多类似工具最后都要你买积分、充会员,这个是纯本地方案
69
+ 2. **质量靠谱** —— 目前最自然的中文 TTS 免费配音
70
+ 3. **门槛极低** —— 会写 Markdown 就会用,不用学 Premiere
71
+ 4. **可控性强** —— 文案改 MD 就行,不用重新录制
72
+
73
+ -----
74
+
75
+
76
+
77
+ # md2video-audio-skill- EN
78
+
79
+ One Markdown file + a one-click command = professional-grade talking-head videos.
80
+
81
+ ✨ What kind of magical skill is `/md2video-audio`?
82
+
83
+ This is a completely free Markdown-to-video skill that directly compiles your `.md` files into MP4 videos with realistic human-like voiceovers:
84
+
85
+ Markdown Document → Marp Slides + Edge-TTS Voice → Finished MP4
86
+
87
+ 🔥 Core Highlights
88
+
89
+ | **Feature** | **Description** |
90
+ | ------------------------ | ------------------------------------------------------------ |
91
+ | 💰 Zero Cost | No OpenAI, no Alibaba Cloud, no paid APIs required. |
92
+ | 🎙️ Realistic Voiceover | Powered by Microsoft Edge-TTS voice, so natural it sounds like a real person. |
93
+ | 🖼️ Auto-Generated Visuals | Uses Marp to render stunning slides as presentation visuals. |
94
+ | ⚡ One-Click Generation | Complete the entire workflow with a single command. |
95
+ | 📦 Standard Output | Directly produces MP4 videos ready to upload to TikTok, Bilibili, or YouTube. |
96
+
97
+ 🚀 How to use? Super simple!
98
+
99
+ - Installation Methods
100
+
101
+ 1. npx installation
102
+
103
+ `npx skills add https://www.modelscope.cn/skills/nihaoModelscope/md2video-audio-skill`
104
+
105
+ 2. SDK installation
106
+
107
+ `pip install --upgrade modelscope modelscope skills add nihaoModelscope/md2video-audio-skill`
108
+
109
+ 3. bash installation
110
+
111
+ `curl -fsSL https://www.modelscope.cn/skills/install.sh | bash -s -- nihaoModelscope/md2video-audio-skill`
112
+
113
+ ## Usage
114
+
115
+ - Type in Claude Code (easily adaptable for other agents):
116
+
117
+ `/md2video-audio your-presentation.md`
118
+
119
+ That's it. No complicated setup, no API keys, no third-party dependencies.
120
+
121
+ - Suitable Scenarios?
122
+
123
+ - Knowledge Sharing: Tech tutorials, reading notes, concept explanations
124
+
125
+ - 💼 Work Reports: Project reviews, data analysis, progress updates
126
+
127
+ - Online Courses: Slide-to-video conversion, micro-lesson production
128
+
129
+ - 📢 Marketing Content: Product introductions, feature demos, event announcements
130
+
131
+ - Personal Branding: Daily video updates, opinion publishing, experience summaries
132
+
133
+ - 💡 Why recommend it?
134
+
135
+ - Truly Free: Many similar tools eventually require buying credits or memberships; this is a purely local solution.
136
+
137
+ - Reliable Quality: free and natural TTS options available.
138
+
139
+ - Extremely Low Barrier: If you can write Markdown, you can use it—no Premiere required.
140
+
141
+ - High Controllability: Just edit the Markdown text to make changes, no need to re-record.
142
+
143
+ ---
144
+
145
+ # Attribution
146
+
147
+ Based on [md2video-audio-skill](https://github.com/70v-Yoyo/md2video-audio-skill) by 70v-Yoyo.
148
+
149
+ Derivative works and redistributions must retain attribution to 70v-Yoyo and include the original repository address.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: md2video-audio
3
+ description: 将指定Markdown文件一键转换为带免费真人配音和讲解画面的 MP4 视频。零成本。当用户要求把 markdown 转成视频、制作口播视频或音视频合成时自动触发。
4
+ ---
5
+
6
+ # Markdown 免费口播视频生成技能 (md2video-audio)
7
+
8
+ - 作用:将本地Markdown 文件自动转化为包含画面与免费语音的 MP4 视频。
9
+
10
+ ## 1. 全局核心铁律 (Core Rules)
11
+
12
+ **必须严格遵守以下原则,任何情况下不得违背:**
13
+
14
+ - **原则一**:**不改原稿**,依次按步骤串行,从原稿到排版与优化稿到展示稿到口播稿,前项生成后项,最终输出视频。
15
+ - **原则二**:严禁擅自安装删除依赖、删除文件、调用脚本及交互输入,不确定时必须向用户确认。
16
+
17
+ ## 2. 串行工作流 (Core Workflow)
18
+
19
+ - 本任务为严格的串行工作流,共分为以下几个阶段。你必须**按顺序推进**,在当前阶段未完成或未通过校验前,**严禁跳跃到后续阶段**。
20
+
21
+ ### 1. 环境依赖检查与安装
22
+
23
+ 对应子文件`references/enviroment.md`
24
+
25
+ ### 2. 生成优化稿
26
+
27
+ - 不改原文件。
28
+ - 操作对象:保存成`new-原名前缀-时间戳`的新md文件
29
+ - 原稿原文内容文字和逻辑不动,只做必要分段分块、增加彩色、前后增加自然引导过渡语。对应子文件`references/newscript.md`
30
+
31
+ ### 3. 生成展示稿
32
+
33
+ - 操作对象:保存成`show-原名前缀-时间戳`的新md文件。
34
+ - 将上个步骤得到的md文件`new-原名前缀-时间戳`拆分为逻辑自然的展示稿,要求:
35
+ 1. Markdown 文件最顶部加上几行配置。这里暂停询问用户要用哪种Marp风格展示,以及是否加入`allowHtml: true`,如果选默认风格见``references/showscript.md`。
36
+ 2. 将HTML标签尽可能都转换为markdown形式表示,如`<img>`转换为`![]()`而里面参数不变
37
+ 3. 根据语义逻辑、md段落结构,用 `---` 分隔每页 PPT。
38
+ - 检查展示稿每页是否符合内容分页规则(如下3条):
39
+ 1. 判断单页内容是否超出页面安全容量(为marp单页高度的85%):展示稿每页内容按不同样式统计行数,结合顶部YAML Front Matter里的style得到不同样式每行内容所占高度,动态估算。如果超过安全容量边界内,则拆分为几个逻辑小单元各占1页(保证每页内容都不超页面安全容量且布局排版好看情况下,数量应尽可能少)
40
+ 2. 判断单块内容所占高度是否超350px:可拆分的(如表格、代码块)将其内部按接近于350px且逻辑自然拆分成多个块。
41
+ 3. 当代码一行过长时按逻辑自然插入换行
42
+ - 检查通过则进入下一阶段,否则根据要求修改不通过地方,直到检查通过。若修改3次仍不通过,则提示用户手动修改后再继续。
43
+
44
+ ### 4. 生成口播稿
45
+
46
+ - 操作对象:保存成`speaking-原名前缀-时间戳`的新md文件
47
+ - 根据展示稿,生成逻辑自然的口播稿:
48
+ 1. 口播稿最开头第一行先加上两个 `---`,换行再写第一页的口播。
49
+ 2. 去掉除分隔符外的乱七八糟的表情图标等仅供展示的念出来不自然的字符。
50
+ - 检查口播稿和展示稿的 `---` 页分隔符数量必须保持一致:正则表达式执行`grep -E '^---[[:space:]]*$' your_file.md | wc -l`。若数量对不上,修改口播稿,每页内容对齐展示稿,一般是在口播稿最开头加入。检查通过则进入下一阶段,否则根据要求修改不通过地方,直到检查通过。若修改3次仍不通过,则提示用户手动修改后再继续。
51
+
52
+ ### 5. 用Python 脚本一键打包成视频
53
+
54
+ - 用上述阶段中生成的2个中间结果md文件,分别是口播稿和展示稿作为输入,调用执行本skill目录下名为`ai-2md2marp2av.py`,`python ai-2md2marp2av.py 展示稿.md 口播稿.md`。注意脚本中有交互输入,必须让用户手动输入。
55
+ - 脚本逻辑为用 Marp 生成的高颜值 PPT 图片后,把图片和 Edge-TTS 生成的语音按对应页数拼起来即可。
56
+
57
+ ### 6. 降级方案
58
+
59
+ - 若生成视频失败,则调用降级方案:执行本skill目录下名为`md2marp2av.py`脚本。若仍失败则执行本skill目录下名为`md2video.py`脚本
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "md2video-audio-skill"
7
+ version = "1.0.0"
8
+ description = "One Markdown file + a one-click command = professional-grade talking-head videos. | 一个 Markdown 文件 + 一键命令 = 专业级口播视频。"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [
13
+ { name = "70v-Yoyo" }
14
+ ]
15
+ keywords = [
16
+ "ai-agent",
17
+ "mcp",
18
+ "claude-code",
19
+ "markdown-to-video",
20
+ "text-to-speech",
21
+ "video-generation"
22
+ ]
23
+ classifiers = [
24
+ "Programming Language :: Python :: 3",
25
+ "License :: OSI Approved :: Apache Software License",
26
+ "Operating System :: OS Independent",
27
+ "Intended Audience :: Developers",
28
+ "Topic :: Multimedia :: Video :: Conversion",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/70v-Yoyo/md2video-audio-skill"
33
+ Bug-Tracker = "https://github.com/70v-Yoyo/md2video-audio-skill"
34
+ Repository = "https://github.com/70v-Yoyo/md2video-audio-skill"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ # 强行让 PyPI 打包当前目录下的所有核心文件,确保 scripts、SKILL.md 和所有 .md 笔记不被过滤
38
+ packages = ["."]
39
+
40
+ [project.scripts]
41
+ md2video = "scripts.ai_2md2marp2av:main"
@@ -0,0 +1,20 @@
1
+ - 检查是否具备相关依赖,若缺失则请求安装:
2
+
3
+ ```bash
4
+ pip install edge-tts moviepy markdown
5
+
6
+ #安装一整套完美对齐、针对 Mermaid 优化的新一代测试版全家桶
7
+ npm install @marp-team/marp-cli@latest \
8
+ @marp-team/marp-core@next \
9
+ shiki \
10
+ beautiful-mermaid \
11
+ katex \
12
+ @mathjax/src \
13
+ @mathjax/mathjax-bbm-font-extension \
14
+ @mathjax/mathjax-bboldx-font-extension \
15
+ @mathjax/mathjax-dsfont-font-extension \
16
+ @mathjax/mathjax-mhchem-font-extension
17
+
18
+ ```
19
+
20
+ (注:若系统提示缺少 ffmpeg,需引导或自动通过 `apt-get install -y ffmpeg` 进行安装)
@@ -0,0 +1,5 @@
1
+ - 语义审阅与排版优化:检查该文件是否用markdown结构化符号按逻辑分段。如果不满足则 **逻辑分段与排版优化**
2
+ - 检查并补全文章的层级标题(确保有 `#` 和 `##` 等作为自然的视觉分镜切换点),每个自然逻辑段用单独行`---`划分
3
+ - 内容优化:
4
+ 1. 合适位置自然放置彩色表情/图标(如emoji)但要保证系统兼容性、HTML能正常渲染出来。
5
+ 2. 代码块里:一行过长按逻辑换行、行数>8则按逻辑拆分成多个行数<8的代码块。代码块外前后补充自然过渡引导语。md文件里正常写mermaid没有报错则不用动它。
@@ -0,0 +1,103 @@
1
+ ````markdown
2
+ ---
3
+ marp: true
4
+ theme: gaia
5
+ _class: default
6
+ paginate: true
7
+ allowHtml: true
8
+ mermaid: true
9
+ style: |
10
+ section {
11
+ /* ===== 全局基准 =====
12
+ YAML block scalar(style: |)禁止用 Tab 做缩进
13
+ */
14
+ font-size: 25px;
15
+ line-height: 1.4;
16
+ /* ===== 页面布局 ===== */
17
+ grid-template-columns: 88%;
18
+ margin: 0 auto;
19
+ padding: 20px;
20
+ display: grid;
21
+ align-content: center;
22
+ justify-content: center; /* 水平居中 */
23
+ }
24
+ /* ===== 代码块 ===== */
25
+ pre {
26
+ font-size: 1em;
27
+ line-height: 1.35;
28
+ margin: 0.5em 0;
29
+ /* 关键:允许内部代码在达到 max-width 时自动换行 */
30
+ white-space: pre-wrap !important;
31
+ word-break: break-word;
32
+ }
33
+
34
+ pre code {
35
+ font-size: 1em;
36
+ white-space: pre-wrap !important; /* 允许在长单词、长行内自动换行 */
37
+ word-break: break-all;
38
+ }
39
+
40
+ /* ===== 标题体系 ===== */
41
+ h1 {
42
+ font-size: 1.8em;
43
+ line-height: 1.15;
44
+ margin: 0 0 0.5em;
45
+ }
46
+ h2 {
47
+ font-size: 1.4em;
48
+ line-height: 1.2;
49
+ margin: 0 0 0.4em;
50
+ }
51
+ h3 {
52
+ font-size: 1.2em;
53
+ line-height: 1.25;
54
+ margin: 0 0 0.3em;
55
+ }
56
+
57
+ /* ===== 正文体系 ===== */
58
+ p {
59
+ margin: 0.5em;
60
+ }
61
+ ul,
62
+ ol {
63
+ margin-top: 0.3em;
64
+ margin-bottom: 0.3em;
65
+ }
66
+ li {
67
+ margin-bottom: 0.25em;
68
+ }
69
+
70
+ /* ===== 图片 ===== */
71
+ img {
72
+ margin:0.1em auto;
73
+ }
74
+ img[alt="mylogo"] {
75
+ display:block;
76
+ width: 130px ;
77
+ height:130px ;
78
+ border-radius: 50%;
79
+ object-fit: contain;
80
+ }
81
+ ---
82
+
83
+ # 核心架构解析
84
+
85
+ ## hi
86
+
87
+ ---
88
+
89
+ ## 章节一:背景介绍
90
+
91
+ - 向量检索与索引平衡
92
+ - HNSW 索引平衡速度和精度
93
+ - IVF_PQ 算法加速
94
+
95
+ ---
96
+
97
+ ## 章节二:核心代码示例
98
+
99
+ ```python
100
+ def hello():
101
+ print("hi")
102
+ ```
103
+ ````
@@ -0,0 +1,194 @@
1
+ import os
2
+ import asyncio
3
+ import re
4
+ from datetime import datetime
5
+ import edge_tts
6
+ from moviepy import ImageClip, AudioFileClip, concatenate_videoclips
7
+ import time
8
+ import random
9
+ from aiohttp.client_exceptions import ClientConnectorError
10
+ import webbrowser
11
+
12
+ VOICE = "zh-CN-YunxiNeural" #女声"zh-CN-XiaoxiaoNeural"
13
+ FPS = 24
14
+
15
+ async def generate_audio(text, output_path):
16
+ communicate = edge_tts.Communicate(text, VOICE)
17
+ await communicate.save(output_path)
18
+
19
+ def build_video_from_files(slide_md_path, script_md_path):
20
+ if not os.path.exists(slide_md_path):
21
+ raise FileNotFoundError(f"找不到画面 Markdown 文件: {slide_md_path}")
22
+ if not os.path.exists(script_md_path):
23
+ raise FileNotFoundError(f"找不到口播稿文件: {script_md_path}")
24
+
25
+ # 获取输出路径(基于画面 md 所在的目录和名称)
26
+ dir_name = os.path.dirname(slide_md_path)
27
+ base_name = os.path.splitext(os.path.basename(slide_md_path))[0]
28
+
29
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
30
+ output_mp4 = os.path.join(dir_name, f"{base_name}_{timestamp}.mp4") if dir_name else f"{base_name}_{timestamp}.mp4"
31
+
32
+ # ============================================================
33
+ # 1. 先导出 HTML,方便检查 Marp 最终生成的 HTML/CSS
34
+ # ============================================================
35
+ print(f"1. 正在调用 Marp 将 Markdown [{slide_md_path}] 导出为 HTML...")
36
+
37
+ html_path = os.path.join(
38
+ dir_name if dir_name else ".",
39
+ f"{base_name}_preview_{timestamp}.html"
40
+ )
41
+
42
+ with open(slide_md_path, "r", encoding="utf-8") as f:
43
+ content = f.read()
44
+ print(content[:3000])
45
+
46
+ html_cmd = (
47
+ f'marp --engine @marp-team/marp-core/full '
48
+ f'--allow-local-files '
49
+ f'--html '
50
+ f'-o "{html_path}" '
51
+ f'"{slide_md_path}"'
52
+ )
53
+
54
+ exit_code = os.system(html_cmd)
55
+
56
+ if exit_code != 0:
57
+ print("错误: Marp HTML 导出失败,请检查 marp-cli 是否正常安装。")
58
+ return
59
+
60
+ print(f"HTML 预览文件已生成:{html_path}")
61
+
62
+ # ============================================================
63
+ # 2. 自动打开 HTML
64
+ # ============================================================
65
+ print("2. 正在打开 HTML 预览...")
66
+ html_abs_path = os.path.abspath(html_path)
67
+ webbrowser.open(f"file://{html_abs_path}")
68
+
69
+ # ============================================================
70
+ # 3. 等待用户确认
71
+ # ============================================================
72
+ while True:
73
+ confirm = input(
74
+ "\n请检查浏览器中的 HTML 预览。\n"
75
+ "确认无误,请输入 ok 继续导出图片:\n" \
76
+ "如果不导出图片,请输入 no 终止程序:\n"
77
+ ).strip().lower()
78
+
79
+ if confirm == "ok":
80
+ break
81
+ elif confirm == "no":
82
+ print("用户选择不导出图片,程序终止。")
83
+ return
84
+ else:
85
+ print("输入无效,请重新输入。")
86
+
87
+ # ============================================================
88
+ # 4. 用户确认后,再导出 PNG
89
+ # ============================================================
90
+
91
+ print(f"1. 正在调用 Marp 将画面 Markdown [{slide_md_path}] 渲染为 PPT 高清图片...")
92
+ # 核心:让 Marp 针对画面 md 生成图片
93
+ exit_code = os.system(f"npx marp --engine @marp-team/marp-core/full --allow-local-files --html --images png --image-scale 2 {slide_md_path}")
94
+
95
+ if exit_code != 0:
96
+ print("错误: Marp 渲染失败,请检查是否安装了 marp-cli 及谷歌浏览器内核。")
97
+ return
98
+
99
+ # 去画面 md 所在的目录下精准搜寻生成的 .png 图片
100
+ target_dir = dir_name if dir_name else "."
101
+ all_files = os.listdir(target_dir)
102
+
103
+ slide_images = sorted([
104
+ os.path.join(target_dir, f) for f in all_files
105
+ if f.startswith(base_name) and f.endswith(".png")
106
+ ])
107
+
108
+ if not slide_images:
109
+ print(f"错误: 在目录 '{target_dir}' 下未找到 Marp 生成的幻灯片图片!")
110
+ return
111
+
112
+ print(f"成功生成 {len(slide_images)} 页幻灯片画面。")
113
+
114
+ # 2. 读取独立的【AI口播稿文件】
115
+ with open(script_md_path, "r", encoding="utf-8") as f:
116
+ script_content = f.read()
117
+
118
+ # 假设口播稿也是按 --- 分割成对应的页数段落
119
+ script_content_body = re.sub(r'^---[\s\S]*?---', '', script_content)
120
+ speech_sections = [s.strip() for s in script_content_body.split("---") if s.strip()]
121
+
122
+ clip_list = []
123
+ temp_audio_files = []
124
+
125
+ print("3. 正在读取独立口播稿,为每一页生成对应的 AI 语音并对齐画面...")
126
+ # 按页数进行 zip 匹配(有多少张 PPT 图片就处理多少段口播)
127
+ for i, img_file in enumerate(slide_images):
128
+ page_num = i + 1
129
+ print(f"正在处理第 {page_num}/{len(slide_images)} 页...")
130
+
131
+ # 优先使用口播稿中对应的段落,如果口播稿段落不够则用默认提示
132
+ if i < len(speech_sections):
133
+ raw_speech = speech_sections[i]
134
+ else:
135
+ raw_speech = f"这是第 {page_num} 部分的内容。"
136
+
137
+ # 清理口播稿中的 markdown 标记,让朗读更自然
138
+ speech_clean = re.sub(r'#+|\*\*|\*|`|```[\s\S]*?```|<[^>]+>', '', raw_speech).strip()
139
+ if not speech_clean:
140
+ speech_clean = f"请看屏幕上的第 {page_num} 页展示。"
141
+
142
+ audio_path = os.path.join(target_dir, f"temp_audio_{base_name}_{page_num}.mp3")
143
+ temp_audio_files.append(audio_path)
144
+
145
+ max_retries = 10 # 最大重试次数
146
+ for attempt in range(max_retries):
147
+ try:
148
+ # 执行原本的生成命令
149
+ asyncio.run(generate_audio(speech_clean, audio_path))
150
+
151
+ # 💡 成功后,随机冷却 2~4 秒,防止连续请求被微软识别为爬虫
152
+ time.sleep(random.uniform(2.0, 4.0))
153
+ break # 成功生成,跳出重试循环
154
+
155
+ except (ClientConnectorError, ConnectionResetError) as e:
156
+ if attempt < max_retries - 1:
157
+ # 💡 失败后,成倍延长等待时间(指数退避策略),等待 10s, 20s, 30s...
158
+ wait_time = (attempt + 1) * 10
159
+ print(f"⚠️ 微软限流或网络断连,将在 {wait_time} 秒后进行第 {attempt + 1} 次重试...")
160
+ time.sleep(wait_time)
161
+ else:
162
+ print("❌ 连续多次重试失败,请检查代理网络。")
163
+ raise e
164
+
165
+
166
+ audio_clip = AudioFileClip(audio_path)
167
+ duration = max(audio_clip.duration, 2.5) # 每页至少停留 2.5 秒
168
+
169
+ # 用标准高颜值图片生成视频片段
170
+ image_clip = ImageClip(img_file).with_duration(duration)
171
+ image_clip = image_clip.with_audio(audio_clip)
172
+ clip_list.append(image_clip)
173
+
174
+ print("4. 正在无缝拼接最终视频...")
175
+ final_video = concatenate_videoclips(clip_list)
176
+ final_video.write_videofile(output_mp4, fps=FPS, codec="libx264", audio_codec="aac")
177
+
178
+ # 清理临时文件(音频及图片)
179
+ print("正在清理临时文件...")
180
+ for f in temp_audio_files:
181
+ if os.path.exists(f): os.remove(f)
182
+ for f in slide_images:
183
+ if os.path.exists(f): os.remove(f)
184
+
185
+ print(f"🎉 完美!使用独立画面与独立口播稿合成的视频已生成: {output_mp4}")
186
+
187
+ if __name__ == "__main__":
188
+ import sys
189
+ # 接收两个参数:第一个是画面 md,第二个是口播稿 md
190
+ # 例如:python marp_to_video.py slide.md script.md
191
+ slide_file = sys.argv[1] if len(sys.argv) > 1 else "slide.md"
192
+ script_file = sys.argv[2] if len(sys.argv) > 2 else "script.md"
193
+
194
+ build_video_from_files(slide_file, script_file)
@@ -0,0 +1,102 @@
1
+ import os
2
+ import asyncio
3
+ import re
4
+ from datetime import datetime
5
+ import edge_tts
6
+ from moviepy import ImageClip, AudioFileClip, concatenate_videoclips
7
+
8
+ # Marp 渲染需要无头 Chromium。本机 chromium 在 /usr/bin/chromium,
9
+ # 通过 CHROME_PATH 显式指定给 marp-cli(避免其自行探测失败)。
10
+ os.environ.setdefault("CHROME_PATH", "/usr/bin/chromium")
11
+
12
+ VOICE = "zh-CN-XiaoxiaoNeural"
13
+ FPS = 24
14
+
15
+ async def generate_audio(text, output_path):
16
+ communicate = edge_tts.Communicate(text, VOICE)
17
+ await communicate.save(output_path)
18
+
19
+ def build_video_from_marp_images(md_file_path):
20
+ if not os.path.exists(md_file_path):
21
+ raise FileNotFoundError(f"找不到文件: {md_file_path}")
22
+
23
+ # 获取输入 md 的绝对或相对路径信息
24
+ dir_name = os.path.dirname(md_file_path)
25
+ base_name = os.path.splitext(os.path.basename(md_file_path))[0]
26
+
27
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
28
+ output_mp4 = os.path.join(dir_name, f"{base_name}_{timestamp}.mp4") if dir_name else f"{base_name}_{timestamp}.mp4"
29
+
30
+ print("1. 正在调用 Marp 将 Markdown 渲染为 PPT 高清图片...")
31
+ # --images png 会把每一页切成独立的高清 PNG 图片
32
+ exit_code = os.system(f"marp '{md_file_path}' --images png")
33
+ if exit_code != 0:
34
+ print("错误: Marp 渲染失败,请检查是否安装了 marp-cli 及谷歌浏览器内核。")
35
+ return
36
+
37
+ # 去 md 所在的目录下精准搜寻生成的 .png 图片
38
+ target_dir = dir_name if dir_name else "."
39
+ all_files = os.listdir(target_dir)
40
+
41
+ # 筛选出以该 markdown 文件名为前缀且以 .png 结尾的图片
42
+ slide_images = sorted([
43
+ os.path.join(target_dir, f) for f in all_files
44
+ if f.startswith(base_name) and f.endswith(".png")
45
+ ])
46
+
47
+ if not slide_images:
48
+ print(f"错误: 在目录 '{target_dir}' 下未找到 Marp 生成的幻灯片图片!")
49
+ return
50
+
51
+ print(f"成功生成 {len(slide_images)} 页幻灯片画面。")
52
+
53
+ # 简单提取每一页的文字用于生成对应语音(这里读取 md 按 --- 分割)
54
+ with open(md_file_path, "r", encoding="utf-8") as f:
55
+ content = f.read()
56
+
57
+ # 过滤掉 frontmatter
58
+ content_body = re.sub(r'^---[\s\S]*?---', '', content)
59
+ slides_text = [s.strip() for s in content_body.split("---") if s.strip()]
60
+
61
+ clip_list = []
62
+ temp_audio_files = []
63
+
64
+ print("2. 正在为每一页生成对应的 AI 口播语音并对齐画面...")
65
+ for i, img_file in enumerate(slide_images):
66
+ page_num = i + 1
67
+ print(f"正在处理第 {page_num}/{len(slide_images)} 页...")
68
+
69
+ # 获取当前页对应的文本,如果没有配对文字则用默认提示
70
+ raw_text = slides_text[i] if i < len(slides_text) else f"这是第 {page_num} 页"
71
+ speech_clean = re.sub(r'#+|\*\*|\*|`|```[\s\S]*?```|<[^>]+>', '', raw_text)
72
+ speech_text = f"接下来我们看第 {page_num} 部分。{speech_clean}".strip()
73
+
74
+ audio_path = f"temp_audio_{page_num}.mp3"
75
+ temp_audio_files.append(audio_path)
76
+
77
+ asyncio.run(generate_audio(speech_text, audio_path))
78
+
79
+ audio_clip = AudioFileClip(audio_path)
80
+ duration = max(audio_clip.duration, 2.5) # 每页至少停留 2.5 秒
81
+
82
+ # 用标准高颜值图片生成视频片段
83
+ image_clip = ImageClip(img_file).with_duration(duration)
84
+ image_clip = image_clip.with_audio(audio_clip)
85
+ clip_list.append(image_clip)
86
+
87
+ print("3. 正在无缝拼接最终视频...")
88
+ final_video = concatenate_videoclips(clip_list)
89
+ final_video.write_videofile(output_mp4, fps=FPS, codec="libx264", audio_codec="aac")
90
+
91
+ # 清理临时文件(音频及图片)
92
+ for f in temp_audio_files:
93
+ if os.path.exists(f): os.remove(f)
94
+ for f in slide_images:
95
+ if os.path.exists(f): os.remove(f)
96
+
97
+ print(f"🎉 完美!高级 PPT 风格视频已生成: {output_mp4}")
98
+
99
+ if __name__ == "__main__":
100
+ import sys
101
+ target_md = sys.argv[1] if len(sys.argv) > 1 else "processed_input.md"
102
+ build_video_from_marp_images(target_md)
@@ -0,0 +1,195 @@
1
+ import asyncio
2
+ import os
3
+ import re
4
+ from datetime import datetime
5
+ import edge_tts
6
+ from moviepy import TextClip, AudioFileClip, CompositeVideoClip, ColorClip, concatenate_videoclips
7
+
8
+ VOICE = "zh-CN-XiaoxiaoNeural"
9
+ WIDTH, HEIGHT = 1280, 720
10
+ FPS = 24
11
+
12
+ def sanitize_text_for_display(text):
13
+ """
14
+ 清洗文本:
15
+ 1. 将 <br> / <br/> 转换为真实的换行符 \n
16
+ 2. 移除其他 HTML 标签(如 <b>, </span> 等)
17
+ 3. 过滤掉会导致中文字体渲染出方块叉的 Emoji 及特殊符号(已修正原始字符串与十六进制范围)
18
+ """
19
+ if not text:
20
+ return ""
21
+
22
+ # 1. 智能转换 HTML 换行标签为真实换行
23
+ text = re.sub(r'<br\s*/?>', '\n', text, flags=re.IGNORECASE)
24
+
25
+ # 2. 移除其他常见的 HTML 标签
26
+ text = re.sub(r'</?[a-zA-Z]+[^>]*>', '', text)
27
+
28
+ # 3. 过滤 Emoji 和特殊符号区块(注意前面加了 r 变成原始字符串,防止转义报错)
29
+ emoji_pattern = re.compile(
30
+ r"["
31
+ r"\U0001f000-\U0001faf9"
32
+ r"\U0001f300-\U0001f5ff"
33
+ r"\U0001f600-\U0001f64f"
34
+ r"\U0001f680-\U0001f6ff"
35
+ r"\U0001f900-\U0001f9ff"
36
+ r"\u2600-\u26ff"
37
+ r"\u2700-\u27bf"
38
+ r"]+", flags=re.UNICODE
39
+ )
40
+ cleaned = emoji_pattern.sub('', text)
41
+
42
+ # 清理多余的水平空格,但保留换行符 \n
43
+ cleaned = re.sub(r'[ \t]+', ' ', cleaned)
44
+ return cleaned.strip()
45
+
46
+ def parse_markdown_by_chapters(md_file_path):
47
+ """
48
+ 按 Markdown 章节结构(以 # 或 ## 标题分割)聚合内容。
49
+ """
50
+ if not os.path.exists(md_file_path):
51
+ raise FileNotFoundError(f"找不到文件: {md_file_path}")
52
+
53
+ with open(md_file_path, "r", encoding="utf-8") as f:
54
+ content = f.read()
55
+
56
+ chapters_raw = re.split(r'(?m)^(#+\s+.+)', content)
57
+
58
+ sections = []
59
+ current_title = "开场简介"
60
+ current_body = []
61
+
62
+ for part in chapters_raw:
63
+ part = part.strip()
64
+ if not part:
65
+ continue
66
+ if part.startswith('#'):
67
+ if current_body:
68
+ sections.append((current_title, "\n".join(current_body)))
69
+ current_body = []
70
+ current_title = re.sub(r'#+\s*', '', part).strip()
71
+ else:
72
+ current_body.append(part)
73
+
74
+ if current_body or current_title:
75
+ sections.append((current_title, "\n".join(current_body)))
76
+
77
+ if not sections and content.strip():
78
+ sections.append(("全文概览", content.strip()))
79
+
80
+ formatted_sections = []
81
+ for title, body in sections:
82
+ # 口播用的文本(去掉 markdown 和 html 标签,保持语气自然)
83
+ speech_clean = re.sub(r'#+|\*\*|\*|`|```[\s\S]*?```|<[^>]+>', '', body)
84
+ speech_clean = f"接下来我们讲解:{title}。{speech_clean}".strip()
85
+
86
+ # 画面展示文本:清洗 HTML 标签、Emoji 及特殊符号
87
+ safe_title = sanitize_text_for_display(title)
88
+ safe_body = sanitize_text_for_display(body)
89
+
90
+ if speech_clean:
91
+ formatted_sections.append((speech_clean, safe_title, safe_body))
92
+
93
+ return formatted_sections
94
+
95
+ async def generate_audio_with_retry(text, output_audio_path, retries=3):
96
+ for attempt in range(retries):
97
+ try:
98
+ communicate = edge_tts.Communicate(text, VOICE)
99
+ await communicate.save(output_audio_path)
100
+ if os.path.exists(output_audio_path) and os.path.getsize(output_audio_path) > 0:
101
+ return True
102
+ except Exception as e:
103
+ print(f" [重试 {attempt+1}/{retries}] 语音生成遇到波动: {e}")
104
+ await asyncio.sleep(2)
105
+ await asyncio.sleep(0.5)
106
+ return False
107
+
108
+ def create_video_from_md(md_file_path):
109
+ dir_name = os.path.dirname(md_file_path)
110
+ base_name = os.path.splitext(os.path.basename(md_file_path))[0]
111
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
112
+ output_mp4 = os.path.join(dir_name, f"{base_name}_{timestamp}.mp4") if dir_name else f"{base_name}_{timestamp}.mp4"
113
+
114
+ print("正在按极简 PPT 规范解析并清洗 Markdown(已适配 <br> 标签转换)...")
115
+ sections = parse_markdown_by_chapters(md_file_path)
116
+
117
+ if not sections:
118
+ print("未提取到有效内容!")
119
+ return
120
+
121
+ clip_list = []
122
+ print(f"共划分为 {len(sections)} 个 PPT 页面,开始高质量渲染...")
123
+
124
+ for i, (speech_text, title_text, body_text) in enumerate(sections):
125
+ print(f"正在处理第 {i+1}/{len(sections)} 页 PPT...")
126
+ temp_audio = f"temp_sec_{i}.mp3"
127
+
128
+ success = asyncio.run(generate_audio_with_retry(speech_text, temp_audio))
129
+ if not success:
130
+ print(f"警告: 第 {i+1} 页语音生成失败,跳过。")
131
+ continue
132
+
133
+ audio_clip = AudioFileClip(temp_audio)
134
+ duration = max(audio_clip.duration, 2.5)
135
+
136
+ # 现代极简微乳白/浅灰色底板 (Slate-50: RGB 248, 250, 252)
137
+ background = ColorClip(size=(WIDTH, HEIGHT), color=(248, 250, 252), duration=duration)
138
+
139
+ clips_on_page = [background]
140
+
141
+ try:
142
+ # 顶部区域:增加左右边距,防止左右截断(两侧各留白 120px,总宽度 1280 - 240 = 1040)
143
+ title_clip = TextClip(
144
+ text=title_text if title_text else "核心内容",
145
+ font="Hiragino Sans GB",
146
+ font_size=34,
147
+ color='#1E3A8A',
148
+ size=(WIDTH - 240, 80),
149
+ method='caption',
150
+ ).with_duration(duration).with_position((120, 70))
151
+ clips_on_page.append(title_clip)
152
+
153
+ # 下方区域:同步调整宽度与左边距,确保正文两侧安全
154
+ body_fontsize = 22 if len(body_text) > 250 else 26
155
+ body_clip = TextClip(
156
+ text=body_text if body_text else "(本章节暂无正文内容)",
157
+ font="Hiragino Sans GB",
158
+ font_size=body_fontsize,
159
+ color='#334155',
160
+ size=(WIDTH - 240, HEIGHT - 220),
161
+ method='caption',
162
+ ).with_duration(duration).with_position((120, 165))
163
+ clips_on_page.append(body_clip)
164
+
165
+ except Exception as e:
166
+ print(f"警告: 页面文字排版失败,降级处理。错误: {e}")
167
+
168
+ sub_video = CompositeVideoClip(clips_on_page)
169
+ sub_video = sub_video.with_audio(audio_clip)
170
+ clip_list.append(sub_video)
171
+
172
+ import time
173
+ time.sleep(0.3)
174
+
175
+ if not clip_list:
176
+ print("错误: 没有成功生成任何有效的视频页面!")
177
+ return
178
+
179
+ print("正在合成最终的极简 PPT 风格视频...")
180
+ final_video = concatenate_videoclips(clip_list)
181
+
182
+ print(f"正在导出最终视频到 {output_mp4}...")
183
+ final_video.write_videofile(output_mp4, fps=FPS, codec="libx264", audio_codec="aac")
184
+
185
+ for i in range(len(sections)):
186
+ tmp = f"temp_sec_{i}.mp3"
187
+ if os.path.exists(tmp):
188
+ os.remove(tmp)
189
+
190
+ print(f"🎉 极简 PPT 风格口播视频生成成功!已保存至: {output_mp4}")
191
+
192
+ if __name__ == "__main__":
193
+ import sys
194
+ target_md = sys.argv[1] if len(sys.argv) > 1 else "processed_input.md"
195
+ create_video_from_md(target_md)