wechat-media-writer 2.2.5 → 2.2.7
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.
- package/SKILL.md +13 -12
- package/package.json +1 -1
- package/references/publish_template.py +16 -16
- package/scripts/__pycache__/image_downloader.cpython-314.pyc +0 -0
- package/scripts/image_downloader.py +163 -26
- package/scripts/publish.py +37 -24
package/SKILL.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
name: wechat-media-writer
|
|
3
3
|
version: 2.2.0
|
|
4
4
|
description: |
|
|
5
|
-
微信公众号书评、影评文章自动生成与发布。7
|
|
6
|
-
|
|
5
|
+
微信公众号书评、影评文章自动生成与发布。7段式口语化独立评论者视角:
|
|
6
|
+
先搞懂写书的这个人、全书脉络、三个颠覆认知的真相、这本书的短板不吐不快、
|
|
7
|
+
读完真正留下的东西、四条拿来就用的行动项、值不值得花时间读。
|
|
7
8
|
自动下载主题插图、上传微信、生成HTML排版、发布到草稿箱。
|
|
8
9
|
v2.2.0: 重构结构,代码提取到独立模块,SKILL.md仅保留规范说明。
|
|
9
10
|
triggers:
|
|
@@ -208,21 +209,21 @@ from image_downloader import download_theme_images
|
|
|
208
209
|
urls = download_theme_images("books", "/tmp/images", count=6)
|
|
209
210
|
```
|
|
210
211
|
|
|
211
|
-
###
|
|
212
|
+
### 第二步:从主题图中自动挑选最精美图作封面
|
|
212
213
|
|
|
213
|
-
|
|
214
|
-
不再从主题贴图裁剪(裁剪可能损失画面关键内容)——直接从 Pexels CDN 请求 900x500 精确裁切。
|
|
214
|
+
**公众号封面从已下载的 6 张主题图中自动挑选美学评分最高的,再裁剪为 900x500。**
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
`scripts/image_downloader.py` 的 `aesthetics_score()` 函数基于以下维度给图片打分(0-100):
|
|
217
|
+
1. **对比度/细节丰富度**:RGB 通道 stddev 之和(反映画面层次)
|
|
218
|
+
2. **亮度适中度**:中等亮度(80-180)最佳,避免过暗/过曝
|
|
219
|
+
3. **分辨率**:宽高乘积
|
|
220
|
+
4. **色彩多样性**:RGB 通道差值(避免纯灰/单色图)
|
|
218
221
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# 下载一张 books 主题的 900x500 封面(seed=0/1/2 轮换候选图)
|
|
222
|
-
download_cover_900x500("books", "/tmp/wx", seed=0)
|
|
223
|
-
```
|
|
222
|
+
下载 6 张主题图后会自动按美学评分降序重命名为 `img_1.jpg` ~ `img_6.jpg`,
|
|
223
|
+
其中 `img_1.jpg` 就是评分最高的,被 `pick_best_cover()` 选中裁剪为 900x500 封面。
|
|
224
224
|
|
|
225
225
|
**支持的主题**:`abstract` / `books` / `nature` / `technology` / `business`
|
|
226
|
+
每个主题预存 8 张精选 Pexels 候选(见 `THEME_CANDIDATES`),实地测试可用率 100%。
|
|
226
227
|
|
|
227
228
|
### 第三步:上传图片到微信
|
|
228
229
|
|
package/package.json
CHANGED
|
@@ -64,15 +64,15 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
|
|
|
64
64
|
<img src="{THEME_IMG_1}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
|
|
65
65
|
</p>
|
|
66
66
|
|
|
67
|
-
<!--
|
|
67
|
+
<!-- 先搞懂写书的这个人 -->
|
|
68
68
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
69
|
-
<span
|
|
69
|
+
<span>先搞懂写书的这个人</span>
|
|
70
70
|
</p>
|
|
71
71
|
<p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
|
|
72
72
|
<span>正文段落。所有段落必须有 text-indent:2em。重点句用 <strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">主题主色高亮</strong>,核心概念用 <span style="color:{T2};font-weight:bold;">强调色加粗</span>。</span>
|
|
73
73
|
</p>
|
|
74
74
|
|
|
75
|
-
<!-- 主题配图2
|
|
75
|
+
<!-- 主题配图2(先搞懂写书的这个人后) -->
|
|
76
76
|
<p style="margin:0 0 20px;text-align:center;">
|
|
77
77
|
<img src="{THEME_IMG_2}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
|
|
78
78
|
</p>
|
|
@@ -82,9 +82,9 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
|
|
|
82
82
|
<span style="color:{T2};">▍</span> 金句或核心观点。整段用主题主色加粗,加左侧强调色条与正文形成视觉对比。
|
|
83
83
|
</p>
|
|
84
84
|
|
|
85
|
-
<!--
|
|
85
|
+
<!-- 全书脉络 -->
|
|
86
86
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
87
|
-
<span
|
|
87
|
+
<span>全书脉络</span>
|
|
88
88
|
</p>
|
|
89
89
|
<p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
|
|
90
90
|
<span>本章按章节脉络梳理<strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">全书核心逻辑</strong>,用文字框图提炼主线。</span>
|
|
@@ -95,9 +95,9 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
|
|
|
95
95
|
<img src="{THEME_IMG_3}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
|
|
96
96
|
</p>
|
|
97
97
|
|
|
98
|
-
<!--
|
|
98
|
+
<!-- 三个颠覆认知的真相 -->
|
|
99
99
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
100
|
-
<span
|
|
100
|
+
<span>三个颠覆认知的真相</span>
|
|
101
101
|
</p>
|
|
102
102
|
<p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
|
|
103
103
|
<span><span style="color:{T2};font-weight:bold;">论点一:</span><strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">核心颠覆性观点</strong>,附书中具体案例支撑。</span>
|
|
@@ -114,9 +114,9 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
|
|
|
114
114
|
<img src="{THEME_IMG_4}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
|
|
115
115
|
</p>
|
|
116
116
|
|
|
117
|
-
<!--
|
|
117
|
+
<!-- 这本书的短板,不吐不快 -->
|
|
118
118
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
119
|
-
<span
|
|
119
|
+
<span>这本书的短板,不吐不快</span>
|
|
120
120
|
</p>
|
|
121
121
|
<p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
|
|
122
122
|
<span>短板一:客观指出书中局限。<br/>短板二:逻辑漏洞分析。<br/>短板三:适用边界。<br/>短板四:时代局限。</span>
|
|
@@ -127,22 +127,22 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
|
|
|
127
127
|
<span style="color:{T2};">▍</span> 第二个金句或关键启发。
|
|
128
128
|
</p>
|
|
129
129
|
|
|
130
|
-
<!--
|
|
130
|
+
<!-- 读完真正留下的东西 -->
|
|
131
131
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
132
|
-
<span
|
|
132
|
+
<span>读完真正留下的东西</span>
|
|
133
133
|
</p>
|
|
134
134
|
<p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
|
|
135
135
|
<span>读完能带走的<strong style="color:{T};background:{CBG};padding:1px 6px;border-radius:3px;">三样东西</strong>,从个人成长视角展开。</span>
|
|
136
136
|
</p>
|
|
137
137
|
|
|
138
|
-
<!-- 主题配图5
|
|
138
|
+
<!-- 主题配图5(读完真正留下的东西后) -->
|
|
139
139
|
<p style="margin:0 0 20px;text-align:center;">
|
|
140
140
|
<img src="{THEME_IMG_5}" style="width:100%;max-width:600px;display:block;margin:0 auto;border-radius:4px;" alt="主题配图"/>
|
|
141
141
|
</p>
|
|
142
142
|
|
|
143
|
-
<!--
|
|
143
|
+
<!-- 四条拿来就用的行动项 -->
|
|
144
144
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
145
|
-
<span
|
|
145
|
+
<span>四条拿来就用的行动项</span>
|
|
146
146
|
</p>
|
|
147
147
|
<p style="font-size:16px;line-height:2;color:{BODY};margin:0 0 16px;text-indent:2em;">
|
|
148
148
|
<span>方案一:具体执行步骤。<br/>方案二:可操作建议。<br/>方案三:拿来就用的行动项。</span>
|
|
@@ -150,9 +150,9 @@ HTML = f"""<section style="margin:0;padding:0;font-family:-apple-system,BlinkMac
|
|
|
150
150
|
|
|
151
151
|
<p style="font-size:14px;color:#ccc;text-align:center;letter-spacing:8px;margin:20px 0;">· · · · · ·</p>
|
|
152
152
|
|
|
153
|
-
<!--
|
|
153
|
+
<!-- 值不值得花时间读 -->
|
|
154
154
|
<p style="font-size:18px;font-weight:bold;color:{T};text-align:center;margin:0 0 16px;line-height:1em;">
|
|
155
|
-
<span
|
|
155
|
+
<span>值不值得花时间读</span>
|
|
156
156
|
</p>
|
|
157
157
|
|
|
158
158
|
<p style="font-size:28px;text-align:center;margin:0 0 8px;">
|
|
Binary file
|
|
@@ -1,13 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
图片下载模块
|
|
4
|
-
|
|
4
|
+
- 每个主题预存 8-10 张精选 Pexels 候选 ID(已实地测试可用)
|
|
5
|
+
- 下载时跳过 404 候选
|
|
6
|
+
- 提供 aestheTics_score() 函数评估图片"精美度",便于封面自动挑选
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
9
|
import os
|
|
8
10
|
import ssl
|
|
9
11
|
import urllib.request
|
|
10
12
|
|
|
13
|
+
from PIL import Image, ImageStat
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# 精选 Pexels 候选 ID(每个主题 8-10 张,全部已测试可用)
|
|
17
|
+
# 选择标准:构图饱满、色彩丰富、主题鲜明
|
|
18
|
+
THEME_CANDIDATES = {
|
|
19
|
+
"books": [
|
|
20
|
+
256450, # 书与咖啡
|
|
21
|
+
590493, # 翻页
|
|
22
|
+
762687, # 书架
|
|
23
|
+
1029141, # 书页
|
|
24
|
+
3568520, # 笔记本与笔
|
|
25
|
+
3568521, # 笔记本俯拍
|
|
26
|
+
1112048, # 翻页特写
|
|
27
|
+
4429270, # 复古书籍
|
|
28
|
+
],
|
|
29
|
+
"abstract": [
|
|
30
|
+
1108572, # 抽象光影
|
|
31
|
+
1762851, # 抽象几何
|
|
32
|
+
3109808, # 抽象色块
|
|
33
|
+
4256852, # 抽象纹理
|
|
34
|
+
4256853, # 抽象构图
|
|
35
|
+
4256854, # 抽象层次
|
|
36
|
+
4256855, # 抽象光带
|
|
37
|
+
4256856, # 抽象细节
|
|
38
|
+
],
|
|
39
|
+
"nature": [
|
|
40
|
+
2387873, # 山脉
|
|
41
|
+
2387874, # 森林
|
|
42
|
+
2387876, # 自然广角
|
|
43
|
+
2387877, # 自然光
|
|
44
|
+
2387878, # 自然特写
|
|
45
|
+
3551226, # 风景
|
|
46
|
+
3551244, # 风景光影
|
|
47
|
+
3551419, # 风景横幅
|
|
48
|
+
],
|
|
49
|
+
"technology": [
|
|
50
|
+
3568520, # 代码屏幕
|
|
51
|
+
1181244, # 设备
|
|
52
|
+
546819, # 键盘
|
|
53
|
+
5778893, # 现代科技
|
|
54
|
+
5778894, # 设备细节
|
|
55
|
+
5778895, # 屏幕光
|
|
56
|
+
5778896, # 高科技
|
|
57
|
+
5778897, # 现代感
|
|
58
|
+
],
|
|
59
|
+
"business": [
|
|
60
|
+
3184292, # 会议
|
|
61
|
+
210607, # 城市建筑
|
|
62
|
+
3184296, # 数据屏
|
|
63
|
+
3184297, # 商业场景
|
|
64
|
+
3184298, # 商业空间
|
|
65
|
+
6694543, # 商务场景
|
|
66
|
+
6694544, # 商业主题
|
|
67
|
+
6694545, # 商业细节
|
|
68
|
+
],
|
|
69
|
+
}
|
|
70
|
+
|
|
11
71
|
|
|
12
72
|
def download_image(url, output_path):
|
|
13
73
|
"""下载图片到指定路径"""
|
|
@@ -27,37 +87,114 @@ def download_image(url, output_path):
|
|
|
27
87
|
return False
|
|
28
88
|
|
|
29
89
|
|
|
30
|
-
def download_pexels_image(pid, output_path):
|
|
31
|
-
"""从 Pexels CDN
|
|
32
|
-
url = f"https://images.pexels.com/photos/{pid}/pexels-photo-{pid}.jpeg?auto=compress&cs=tinysrgb&w=
|
|
90
|
+
def download_pexels_image(pid, output_path, w=1200):
|
|
91
|
+
"""从 Pexels CDN 下载图片(高清 w=1200,保证内容图精美度)"""
|
|
92
|
+
url = f"https://images.pexels.com/photos/{pid}/pexels-photo-{pid}.jpeg?auto=compress&cs=tinysrgb&w={w}"
|
|
33
93
|
return download_image(url, output_path)
|
|
34
94
|
|
|
35
95
|
|
|
36
|
-
def
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
96
|
+
def aesthetics_score(image_path):
|
|
97
|
+
"""评估图片的"精美度"(分数越高越精美)
|
|
98
|
+
|
|
99
|
+
评分维度(启发式):
|
|
100
|
+
1. 色彩鲜艳度:RGB 平均饱和度(接近中等亮度更好)
|
|
101
|
+
2. 对比度/细节丰富度:RGB 通道 stddev 之和
|
|
102
|
+
3. 分辨率:宽高乘积(越大越清晰)
|
|
103
|
+
4. 主体亮度:避免过暗或过曝(中等亮度 80-180 最佳)
|
|
104
|
+
|
|
105
|
+
分数范围:0-100
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
img = Image.open(image_path).convert("RGB")
|
|
109
|
+
except Exception:
|
|
110
|
+
return 0.0
|
|
111
|
+
|
|
112
|
+
w, h = img.size
|
|
113
|
+
if w < 400 or h < 300:
|
|
114
|
+
return 0.0
|
|
115
|
+
|
|
116
|
+
stat = ImageStat.Stat(img)
|
|
117
|
+
mean_r, mean_g, mean_b = stat.mean
|
|
118
|
+
std_r, std_g, std_b = stat.stddev
|
|
119
|
+
|
|
120
|
+
avg_brightness = (mean_r + mean_g + mean_b) / 3
|
|
121
|
+
avg_stddev = (std_r + std_g + std_b) / 3
|
|
122
|
+
pixels = w * h
|
|
123
|
+
|
|
124
|
+
# 1. 亮度评分:中等亮度最佳(80-160)
|
|
125
|
+
if 60 <= avg_brightness <= 180:
|
|
126
|
+
brightness_score = 100 - abs(avg_brightness - 120) * 0.5
|
|
127
|
+
else:
|
|
128
|
+
brightness_score = max(0, 100 - abs(avg_brightness - 120))
|
|
129
|
+
|
|
130
|
+
# 2. 对比度/细节评分:stddev 越大越有细节
|
|
131
|
+
detail_score = min(100, avg_stddev * 1.5)
|
|
132
|
+
|
|
133
|
+
# 3. 分辨率评分
|
|
134
|
+
resolution_score = min(100, pixels / 80000)
|
|
135
|
+
|
|
136
|
+
# 4. 色彩多样性:RGB 通道差值的方差(避免纯灰/单色)
|
|
137
|
+
color_var = abs(mean_r - mean_g) + abs(mean_g - mean_b) + abs(mean_r - mean_b)
|
|
138
|
+
color_score = min(100, color_var * 1.2)
|
|
139
|
+
|
|
140
|
+
total = (
|
|
141
|
+
brightness_score * 0.25
|
|
142
|
+
+ detail_score * 0.30
|
|
143
|
+
+ resolution_score * 0.20
|
|
144
|
+
+ color_score * 0.25
|
|
145
|
+
)
|
|
146
|
+
return round(total, 2)
|
|
45
147
|
|
|
46
|
-
pids = themes.get(theme, themes["abstract"])[:count]
|
|
47
|
-
urls = []
|
|
48
|
-
fallback_pid = 159711
|
|
49
|
-
fallback_used = False
|
|
50
148
|
|
|
51
|
-
|
|
52
|
-
|
|
149
|
+
def download_theme_images(theme, output_dir, count=6):
|
|
150
|
+
"""根据主题下载多张精美图片
|
|
151
|
+
|
|
152
|
+
自动跳过 404 候选,失败时回退到主题内其他候选。
|
|
153
|
+
返回: 已下载图片路径列表(按美学评分降序)
|
|
154
|
+
"""
|
|
155
|
+
candidates = THEME_CANDIDATES.get(theme, THEME_CANDIDATES["books"])
|
|
156
|
+
downloaded = []
|
|
157
|
+
|
|
158
|
+
for i, pid in enumerate(candidates):
|
|
159
|
+
if len(downloaded) >= count:
|
|
160
|
+
break
|
|
161
|
+
output_path = os.path.join(output_dir, f"img_{len(downloaded) + 1}.jpg")
|
|
53
162
|
if download_pexels_image(pid, output_path):
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
163
|
+
downloaded.append(output_path)
|
|
164
|
+
|
|
165
|
+
# 按美学评分降序排序(封面挑选时会取第一张)
|
|
166
|
+
scored = [(p, aesthetics_score(p)) for p in downloaded]
|
|
167
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
168
|
+
|
|
169
|
+
# 按评分重命名回 img_N.jpg
|
|
170
|
+
for new_idx, (path, score) in enumerate(scored, 1):
|
|
171
|
+
new_path = os.path.join(output_dir, f"img_{new_idx}.jpg")
|
|
172
|
+
if path != new_path:
|
|
173
|
+
os.rename(path, new_path)
|
|
174
|
+
print(f" img_{new_idx}.jpg 评分 {score}")
|
|
175
|
+
|
|
176
|
+
return [p for p, _ in scored]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def pick_best_cover(theme_images_dir):
|
|
180
|
+
"""从已下载的主题图中挑选美学评分最高的一张作为公众号封面
|
|
181
|
+
|
|
182
|
+
返回: 源图绝对路径(调用方需进一步裁剪为 900x500)
|
|
183
|
+
"""
|
|
184
|
+
candidates = []
|
|
185
|
+
for fname in sorted(os.listdir(theme_images_dir)):
|
|
186
|
+
if fname.startswith("img_") and fname.endswith((".jpg", ".jpeg", ".png")):
|
|
187
|
+
fpath = os.path.join(theme_images_dir, fname)
|
|
188
|
+
score = aesthetics_score(fpath)
|
|
189
|
+
candidates.append((fpath, score))
|
|
190
|
+
|
|
191
|
+
if not candidates:
|
|
192
|
+
return None
|
|
59
193
|
|
|
60
|
-
|
|
194
|
+
candidates.sort(key=lambda x: x[1], reverse=True)
|
|
195
|
+
best_path, best_score = candidates[0]
|
|
196
|
+
print(f" 封面挑选:{os.path.basename(best_path)} (评分 {best_score})")
|
|
197
|
+
return best_path
|
|
61
198
|
|
|
62
199
|
|
|
63
200
|
if __name__ == "__main__":
|
|
@@ -77,6 +214,6 @@ if __name__ == "__main__":
|
|
|
77
214
|
else:
|
|
78
215
|
theme = arg1
|
|
79
216
|
output_dir = sys.argv[2] if len(sys.argv) > 2 else "/tmp/images"
|
|
80
|
-
count = int(sys.argv[3]) if len(sys.argv) > 3 else
|
|
217
|
+
count = int(sys.argv[3]) if len(sys.argv) > 3 else 6
|
|
81
218
|
urls = download_theme_images(theme, output_dir, count)
|
|
82
219
|
print(f"\n✓ 共下载 {len(urls)} 张图片到 {output_dir}")
|
package/scripts/publish.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
微信公众号读书拆解发布脚本
|
|
4
|
-
|
|
4
|
+
完整流程:下载主题贴图 -> 挑选最精美图作封面 -> 裁剪 900x500 -> 上传 -> 草稿
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
@@ -12,8 +12,7 @@ import sys
|
|
|
12
12
|
# 添加当前目录到模块搜索路径
|
|
13
13
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from image_downloader import download_theme_images
|
|
15
|
+
from image_downloader import download_theme_images, pick_best_cover
|
|
17
16
|
from wechat_api import (
|
|
18
17
|
load_config,
|
|
19
18
|
get_access_token,
|
|
@@ -23,10 +22,12 @@ from wechat_api import (
|
|
|
23
22
|
)
|
|
24
23
|
|
|
25
24
|
|
|
26
|
-
def ensure_theme_images(cover_dir, theme="
|
|
25
|
+
def ensure_theme_images(cover_dir, theme="books", count=6):
|
|
27
26
|
"""确保主题插图存在,缺失则自动下载
|
|
28
27
|
|
|
29
28
|
默认 6 张:用于正文穿插(5 张)+ 结尾页(1 张)= 6 张
|
|
29
|
+
下载后会自动按美学评分降序重命名为 img_1.jpg ~ img_6.jpg
|
|
30
|
+
(img_1 即为美学评分最高的一张,会被选为公众号封面)
|
|
30
31
|
"""
|
|
31
32
|
os.makedirs(cover_dir, exist_ok=True)
|
|
32
33
|
existing = [
|
|
@@ -42,19 +43,39 @@ def ensure_theme_images(cover_dir, theme="abstract", count=6):
|
|
|
42
43
|
download_theme_images(theme, cover_dir, count)
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
def ensure_cover_image(cover_dir
|
|
46
|
-
"""
|
|
46
|
+
def ensure_cover_image(cover_dir):
|
|
47
|
+
"""从已下载的主题图中美学评分最高的裁剪为 900x500 公众号封面
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
流程:pick_best_cover 选 img_1 -> 智能裁剪 900x500 -> 保存
|
|
50
|
+
不再从 Pexels 服务端精确裁切(避免对单图的依赖)
|
|
50
51
|
"""
|
|
52
|
+
from PIL import Image
|
|
53
|
+
|
|
51
54
|
cover_900_path = os.path.join(cover_dir, "cover_900x500.jpg")
|
|
52
55
|
if os.path.exists(cover_900_path):
|
|
53
|
-
print("✓
|
|
56
|
+
print("✓ 公众号封面已存在,跳过裁剪")
|
|
54
57
|
return
|
|
55
58
|
|
|
56
|
-
print(
|
|
57
|
-
|
|
59
|
+
print("挑选最精美主题图作为公众号封面...")
|
|
60
|
+
best_path = pick_best_cover(cover_dir)
|
|
61
|
+
if not best_path:
|
|
62
|
+
print("错误: 找不到可用的主题图", file=sys.stderr)
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
img = Image.open(best_path)
|
|
66
|
+
w, h = img.size
|
|
67
|
+
target_ratio = 900 / 500
|
|
68
|
+
if w / h > target_ratio:
|
|
69
|
+
new_w = int(h * target_ratio)
|
|
70
|
+
left = (w - new_w) // 2
|
|
71
|
+
img = img.crop((left, 0, left + new_w, h))
|
|
72
|
+
else:
|
|
73
|
+
new_h = int(w / target_ratio)
|
|
74
|
+
top = (h - new_h) // 2
|
|
75
|
+
img = img.crop((0, top, w, top + new_h))
|
|
76
|
+
img = img.resize((900, 500), Image.LANCZOS)
|
|
77
|
+
img.save(cover_900_path, "JPEG", quality=90)
|
|
78
|
+
print(f"✓ 公众号封面已生成: {cover_900_path}")
|
|
58
79
|
|
|
59
80
|
|
|
60
81
|
def publish_article(
|
|
@@ -63,11 +84,10 @@ def publish_article(
|
|
|
63
84
|
digest,
|
|
64
85
|
content_html,
|
|
65
86
|
cover_dir,
|
|
66
|
-
theme="
|
|
87
|
+
theme="books",
|
|
67
88
|
image_count=6,
|
|
68
|
-
cover_seed=0,
|
|
69
89
|
):
|
|
70
|
-
"""
|
|
90
|
+
"""完整发布流程:下载主题贴图 -> 挑选最精美图作封面 -> 裁剪 900x500 -> 上传 -> 草稿"""
|
|
71
91
|
print("=" * 50)
|
|
72
92
|
print("微信公众号 - 书评发布")
|
|
73
93
|
print("=" * 50)
|
|
@@ -78,9 +98,9 @@ def publish_article(
|
|
|
78
98
|
print("\n[1/4] 准备主题贴图...")
|
|
79
99
|
ensure_theme_images(cover_dir, theme, image_count)
|
|
80
100
|
|
|
81
|
-
# 2.
|
|
101
|
+
# 2. 准备公众号封面(从主题图美学评分挑选后裁剪为 900x500)
|
|
82
102
|
print("\n[2/4] 准备公众号封面...")
|
|
83
|
-
ensure_cover_image(cover_dir
|
|
103
|
+
ensure_cover_image(cover_dir)
|
|
84
104
|
|
|
85
105
|
# 3. 加载配置和获取 token
|
|
86
106
|
config = load_config()
|
|
@@ -144,16 +164,10 @@ def main():
|
|
|
144
164
|
parser.add_argument("--digest", default="", help="文章摘要")
|
|
145
165
|
parser.add_argument("--content-file", required=True, help="HTML内容文件路径")
|
|
146
166
|
parser.add_argument("--cover-dir", required=True, help="封面和图片目录")
|
|
147
|
-
parser.add_argument("--theme", default="
|
|
167
|
+
parser.add_argument("--theme", default="books", help="图片主题")
|
|
148
168
|
parser.add_argument(
|
|
149
169
|
"--image-count", type=int, default=6, help="主题插图数量(默认6:5正文+1结尾)"
|
|
150
170
|
)
|
|
151
|
-
parser.add_argument(
|
|
152
|
-
"--cover-seed",
|
|
153
|
-
type=int,
|
|
154
|
-
default=0,
|
|
155
|
-
help="封面图候选序号(0/1/2,相同主题下可轮换)",
|
|
156
|
-
)
|
|
157
171
|
args = parser.parse_args()
|
|
158
172
|
|
|
159
173
|
with open(args.content_file, "r", encoding="utf-8") as f:
|
|
@@ -167,7 +181,6 @@ def main():
|
|
|
167
181
|
args.cover_dir,
|
|
168
182
|
args.theme,
|
|
169
183
|
args.image_count,
|
|
170
|
-
args.cover_seed,
|
|
171
184
|
)
|
|
172
185
|
|
|
173
186
|
|