247 lines
11 KiB
Python
247 lines
11 KiB
Python
import subprocess
|
||
|
||
import markdown
|
||
from playwright.async_api import async_playwright
|
||
import os
|
||
import asyncio
|
||
|
||
|
||
# linux 下需要安装字体
|
||
# sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra
|
||
# sudo apt-get install -y fonts-noto-color-emoji fonts-noto-cjk fonts-wqy-microhei
|
||
# 将 Markdown 字符串转换为 HTML
|
||
def md_str_to_html(md_content, output_html):
|
||
"""
|
||
将 Markdown 字符串转换为 HTML 文件,并添加支持中文和 Emoji 的样式。
|
||
|
||
:param md_content: 输入的 Markdown 字符串
|
||
:param output_html: 输出的 HTML 文件路径
|
||
"""
|
||
# 转换 Markdown 为 HTML,启用额外功能(如表格、代码高亮)
|
||
html_content = markdown.markdown(md_content, extensions=['extra', 'codehilite'])
|
||
|
||
# 添加基本的 HTML 结构和样式,支持中文和 Emoji
|
||
css = """
|
||
<style>
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', 'Noto Sans CJK SC', 'Microsoft YaHei', sans-serif;
|
||
padding: 20px 25px;
|
||
line-height: 1.6;
|
||
max-width: 750px;
|
||
margin: 0 auto;
|
||
background-color: #f9f9f9;
|
||
color: #333;
|
||
border: 1px solid #f0f0f0;
|
||
font-size: 16px;
|
||
}
|
||
h1, h2, h3, h4, h5, h6 {
|
||
color: #222;
|
||
margin-top: 24px;
|
||
margin-bottom: 16px;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
}
|
||
h1 {
|
||
font-size: 2.2em;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #eee;
|
||
text-align: center;
|
||
margin-bottom: 25px;
|
||
color: #1a1a1a;
|
||
}
|
||
h2 {
|
||
font-size: 1.8em;
|
||
padding-bottom: 10px;
|
||
margin-top: 30px;
|
||
border-bottom: 1px solid #eee;
|
||
color: #2c3e50;
|
||
}
|
||
h3 {
|
||
font-size: 1.5em;
|
||
margin-top: 25px;
|
||
padding-left: 12px;
|
||
border-left: 4px solid #ddd;
|
||
color: #34495e;
|
||
}
|
||
pre, code {
|
||
background-color: #f5f5f5;
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
font-family: 'Courier New', Courier, monospace;
|
||
font-size: 0.95em;
|
||
border: 1px solid #eee;
|
||
}
|
||
table {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
margin: 18px 0;
|
||
background-color: white;
|
||
}
|
||
th, td {
|
||
border: 1px solid #eee;
|
||
padding: 10px 12px;
|
||
text-align: left;
|
||
}
|
||
th {
|
||
background-color: #fafafa;
|
||
font-weight: 600;
|
||
}
|
||
/* 确保 Emoji 正确渲染 */
|
||
span, p, li {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', 'Noto Sans CJK SC', 'Microsoft YaHei', sans-serif;
|
||
font-size: 16px;
|
||
}
|
||
p {
|
||
margin: 16px 0;
|
||
color: #333;
|
||
line-height: 1.8;
|
||
font-size: 16px;
|
||
}
|
||
ul, ol {
|
||
padding-left: 25px;
|
||
margin: 18px 0;
|
||
}
|
||
li {
|
||
margin: 8px 0;
|
||
line-height: 1.7;
|
||
font-size: 16px;
|
||
}
|
||
blockquote {
|
||
margin: 18px 0;
|
||
padding: 12px 18px;
|
||
background-color: #f8f8f8;
|
||
border-left: 5px solid #ddd;
|
||
color: #555;
|
||
font-size: 1em;
|
||
}
|
||
/* 强调样式 */
|
||
strong {
|
||
color: #222;
|
||
font-weight: 600;
|
||
}
|
||
/* 链接样式 */
|
||
a {
|
||
color: #3498db;
|
||
text-decoration: none;
|
||
}
|
||
a:hover {
|
||
text-decoration: underline;
|
||
color: #2980b9;
|
||
}
|
||
/* 星级评分样式 */
|
||
h3 em {
|
||
color: #fa8c16;
|
||
font-style: normal;
|
||
font-size: 1.1em;
|
||
}
|
||
/* 时间和标签样式 */
|
||
.time, .tag {
|
||
color: #777;
|
||
font-size: 0.95em;
|
||
}
|
||
/* 底部署名样式 */
|
||
.signature {
|
||
margin-top: 35px;
|
||
text-align: right;
|
||
color: #777;
|
||
font-size: 0.95em;
|
||
font-style: italic;
|
||
}
|
||
</style>
|
||
"""
|
||
|
||
# 写入 HTML 文件
|
||
with open(output_html, 'w', encoding='utf-8') as f:
|
||
f.write('<html><head>')
|
||
f.write('<meta charset="UTF-8">') # 确保 UTF-8 编码
|
||
f.write(css)
|
||
f.write('</head><body>')
|
||
f.write(html_content)
|
||
f.write('</body></html>')
|
||
|
||
|
||
def check_chromium_installed(path):
|
||
return os.path.isfile(path) and os.access(path, os.X_OK)
|
||
|
||
|
||
async def html_to_image(html_file, output_image):
|
||
"""
|
||
使用 Playwright 加载 HTML 文件并截图(异步)。
|
||
"""
|
||
try:
|
||
async with async_playwright() as p:
|
||
browser_path = None
|
||
|
||
if os.name == 'nt': # Windows
|
||
possible_chrome_paths = [
|
||
r"C:\Users\Liu_WIN10\AppData\Local\Google\Chrome\Application\chrome.exe",
|
||
r"C:\Users\Liu-OPEN\AppData\Local\Google\Chrome\Application\chrome.exe",
|
||
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
|
||
]
|
||
for path in possible_chrome_paths:
|
||
if check_chromium_installed(path):
|
||
browser_path = path
|
||
print(f"找到浏览器路径: {browser_path}")
|
||
break
|
||
else: # Linux
|
||
import glob
|
||
user_home = os.path.expanduser("~")
|
||
glob_pattern = os.path.join(user_home, ".cache", "ms-playwright", "chromium-*", "chrome-linux",
|
||
"chrome")
|
||
chrome_paths = glob.glob(glob_pattern)
|
||
browser_path = None
|
||
for path in sorted(chrome_paths, reverse=True): # 按版本名排序,最新优先
|
||
if check_chromium_installed(path):
|
||
browser_path = path
|
||
print(f"找到 Playwright Chromium 路径: {browser_path}")
|
||
break
|
||
|
||
if not browser_path:
|
||
print("未找到已安装的 Chromium 浏览器,尝试使用 Playwright 默认安装")
|
||
try:
|
||
print("正在安装 Playwright 浏览器...")
|
||
subprocess.run(["playwright", "install", "chromium"], check=True)
|
||
print("Playwright 浏览器安装完成")
|
||
except Exception as install_error:
|
||
print(f"安装 Playwright 浏览器失败: {install_error}")
|
||
|
||
browser = await p.chromium.launch() # 使用默认路径
|
||
else:
|
||
browser = await p.chromium.launch(executable_path=browser_path)
|
||
|
||
# 业务逻辑不变
|
||
page = await browser.new_page()
|
||
await page.goto(f'file://{os.path.abspath(html_file)}')
|
||
await page.set_viewport_size({"width": 750, "height": 800})
|
||
await page.wait_for_timeout(500)
|
||
await page.screenshot(path=output_image, full_page=True)
|
||
await browser.close()
|
||
|
||
except Exception as e:
|
||
print(f"浏览器操作失败: {e}")
|
||
if "Executable doesn't exist" in str(e):
|
||
print("请运行 'playwright install' 命令安装必要的浏览器组件")
|
||
raise
|
||
|
||
|
||
# 主函数:从字符串转换 Markdown 到图片(异步版)
|
||
async def convert_md_str_to_image(md_content, output_image):
|
||
"""
|
||
将 Markdown 字符串转换为图片(异步)。
|
||
"""
|
||
temp_html = 'temp_output.html'
|
||
md_str_to_html(md_content, temp_html)
|
||
await html_to_image(temp_html, output_image)
|
||
os.remove(temp_html)
|
||
print(f"图片已生成:{output_image}")
|
||
return os.path.abspath(output_image)
|
||
|
||
|
||
# 示例使用
|
||
if __name__ == "__main__":
|
||
# 示例 Markdown 字符串(包含中文和 Emoji)
|
||
md_content = """### Python的类型\n1. **数字类型**:\n - **整数(int)**:用于表示整数,例如`5`、`-10`。在Python 3中,整数的长度不受限制,可以表示任意大小的整数。\n - **浮点数(float)**:用于表示带有小数部分的数字,例如`3.14`、`-0.5`。它采用IEEE 754标准来存储,可能会存在精度问题。\n - **复数(complex)**:由实数部分和虚数部分组成,例如`3 + 4j`,其中`j`表示虚数单位。\n2. **序列类型**:\n - **字符串(str)**:用于表示文本,是不可变的字符序列,例如`'Hello, World!'`、`\"Python\"`。可以通过索引和切片操作访问其中的字符。\n - **列表(list)**:是可变的有序序列,可以包含不同类型的元素,例如`[1, 'apple', 3.14]`。支持添加、删除、修改元素等操作。\n - **元组(tuple)**:与列表类似,但它是不可变的有序序列,例如`(1, 'apple', 3.14)`。一旦创建,其元素不能被修改。\n3. **映射类型**:\n - **字典(dict)**:是一种无序的键值对集合,用于存储和检索数据,例如`{'name': 'Alice', 'age': 25}`。键必须是唯一且不可变的,值可以是任意类型。\n4. **集合类型**:\n - **集合(set)**:是一个无序的不重复元素集合,例如`{1, 2, 3}`。常用于去重和数学集合运算,如并集、交集、差集等。\n - **冻结集合(frozenset)**:与集合类似,但它是不可变的,一旦创建就不能修改。\n\n### 将时间戳转换为str\n在Python中,可以使用`datetime`模块来完成时间戳转换为字符串的操作。以下是示例代码:\n\n```python\nimport datetime\n\n\n# 假设时间戳为秒级时间戳\ntimestamp = 1672531200\n# 将时间戳转换为datetime对象\ndt = datetime.datetime.fromtimestamp(timestamp)\n# 将datetime对象格式化为字符串\nstr_time = dt.strftime('%Y-%m-%d %H:%M:%S')\nprint(str_time)\n```\n\n在上述代码中:\n1. `import datetime`导入`datetime`模块。\n2. `datetime.datetime.fromtimestamp(timestamp)`将时间戳(这里假设为秒级时间戳)转换为`datetime`对象。\n3. `dt.strftime('%Y-%m-%d %H:%M:%S')`使用`strftime`方法将`datetime`对象格式化为指定格式的字符串,`%Y`表示四位数的年份,`%m`表示两位数的月份,`%d`表示两位数的日期,`%H`表示24小时制的小时,`%M`表示分钟,`%S`表示秒。\n\n如果时间戳是毫秒级的,需要先将其转换为秒级,例如:\n\n```python\nimport datetime\n\n\n# 假设时间戳为毫秒级时间戳\ntimestamp = 1672531200000\n# 将毫秒级时间戳转换为秒级\ntimestamp_seconds = timestamp / 1000\n# 将时间戳转换为datetime对象\ndt = datetime.datetime.fromtimestamp(timestamp_seconds)\n# 将datetime对象格式化为字符串\nstr_time = dt.strftime('%Y-%m-%d %H:%M:%S')\nprint(str_time)\n"""
|
||
spath = asyncio.run(convert_md_str_to_image(md_content, "output.png"))
|
||
print(spath)
|