1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
| import os
import re
import subprocess
import time
import urllib.parse
from pathlib import Path
# 配置参数(请根据实际情况修改)
BLOG_ROOT = r"D:\Hugo\blog"
POSTS_DIR = os.path.join(BLOG_ROOT, "content", "posts")
UPLOADS_DIR = os.path.join(BLOG_ROOT, "assets", "uploads") # 手动下载位置
IMAGE_DIR = os.path.join(BLOG_ROOT, "assets", "images") # 最终存储位置
PICLIST_PATH = r"D:\Program Files\PicList\PicList.exe"
IMAGE_EXTENSIONS = ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg')
def is_in_code_block(line, in_code_block):
"""检测是否在代码块中(基于代码块标记)"""
if re.match(r'^\s*```', line):
return not in_code_block
return in_code_block
def contains_code_segment(line):
"""检测行内代码段(反引号包围的内容)"""
# 找出所有行内代码段的位置
code_segments = []
pos = 0
while pos < len(line):
start = line.find('`', pos)
if start == -1:
break
end = line.find('`', start + 1)
if end == -1: # 未闭合的反引号
break
code_segments.append((start, end))
pos = end + 1
return code_segments
def protect_code_content(line, in_code_block):
"""
保护代码内容不被修改
返回:处理后的行,以及新的代码块状态
"""
# 如果在代码块中,完全保护整行
if in_code_block:
return line, in_code_block, True
# 检测行内代码段
code_segments = contains_code_segment(line)
if not code_segments:
return line, in_code_block, False
# 保护行内代码段
protected_line = ""
last_pos = 0
for start, end in code_segments:
# 添加非代码内容
protected_line += line[last_pos:start]
# 添加受保护的代码段(原样保留)
protected_line += line[start:end+1]
last_pos = end + 1
# 添加剩余内容
protected_line += line[last_pos:]
return protected_line, in_code_block, True
def upload_images():
"""使用PicList命令行批量上传图片"""
print(f"开始上传图片到 {IMAGE_DIR}")
# 确保目标目录存在
os.makedirs(IMAGE_DIR, exist_ok=True)
# 遍历上传目录中的所有图片
uploaded_count = 0
for root, _, files in os.walk(UPLOADS_DIR):
for file in files:
if file.lower().endswith(IMAGE_EXTENSIONS):
file_path = os.path.join(root, file)
# 构建PicList上传命令
cmd = [PICLIST_PATH, "upload", file_path]
try:
# 执行上传
result = subprocess.run(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30,
check=True
)
# 输出上传结果
print(f"✅ 上传成功: {file}")
uploaded_count += 1
except subprocess.CalledProcessError as e:
print(f"❌ 上传失败: {file}")
print(f"错误: {e.stderr}")
except subprocess.TimeoutExpired:
print(f"❌ 上传超时: {file}")
except Exception as e:
print(f"❌ 未知错误: {file} - {str(e)}")
print(f"\n✅ 上传完成! 成功上传 {uploaded_count} 张图片")
return uploaded_count
def replace_image_links_step1():
"""第一步:替换图床URL为Hugo兼容的绝对路径(使用正斜杠)"""
print("\n第一步:替换图床URL为绝对路径...")
updated_files = 0
# 使用正斜杠路径 (Hugo兼容)
abs_path_prefix = str(Path(IMAGE_DIR).as_posix()) + '/'
for root, _, files in os.walk(POSTS_DIR):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
with open(file_path, 'r+', encoding='utf-8') as f:
content = f.read()
original_content = content
# 逐行处理,保护代码块和行内代码中的内容
lines = content.split('\n')
new_lines = []
in_code_block = False
for line in lines:
# 检测代码块状态
in_code_block = is_in_code_block(line, in_code_block)
# 保护代码内容
protected_line, in_code_block, is_protected = protect_code_content(line, in_code_block)
if not is_protected:
# 匹配常见的图床URL模式
pattern = r'!\[(.*?)\]\((https?://[^\s\)]+)\)'
# 使用函数处理每个匹配项
def replace_match(match):
alt_text = match.group(1)
url = match.group(2)
# 从URL中提取文件名
filename = os.path.basename(urllib.parse.urlparse(url).path)
# 构建Hugo兼容的绝对路径
return f''
# 替换非保护内容中的图片链接
protected_line = re.sub(pattern, replace_match, protected_line)
new_lines.append(protected_line)
new_content = '\n'.join(new_lines)
# 保存更新
if new_content != original_content:
f.seek(0)
f.write(new_content)
f.truncate()
print(f"更新: {file}")
updated_files += 1
print(f"✅ 第一步完成! 更新了 {updated_files} 个文件中的链接")
return updated_files
def replace_image_links_step2():
"""第二步:替换绝对路径为相对路径(使用正斜杠)"""
print("\n第二步:替换绝对路径为相对路径...")
updated_files = 0
# 使用与第一步相同的路径格式 (正斜杠)
abs_path_prefix = str(Path(IMAGE_DIR).as_posix()) + '/'
rel_path = "../../assets/images/" # Hugo兼容的相对路径
for root, _, files in os.walk(POSTS_DIR):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
with open(file_path, 'r+', encoding='utf-8') as f:
content = f.read()
original_content = content
lines = content.split('\n')
new_lines = []
in_code_block = False
for line in lines:
in_code_block = is_in_code_block(line, in_code_block)
protected_line, in_code_block, is_protected = protect_code_content(line, in_code_block)
if not is_protected:
# 调试输出
if abs_path_prefix in protected_line:
print(f"在 {file} 中找到需要替换的路径: {protected_line.strip()[:50]}...")
# 替换绝对路径为相对路径
protected_line = protected_line.replace(abs_path_prefix, rel_path)
new_lines.append(protected_line)
new_content = '\n'.join(new_lines)
if new_content != original_content:
f.seek(0)
f.write(new_content)
f.truncate()
print(f"更新: {file}")
updated_files += 1
print(f"✅ 第二步完成! 更新了 {updated_files} 个文件中的链接")
return updated_files
def main():
print("=" * 60)
print("Hugo 图片迁移回本地工具 - 智能代码保护版")
print("=" * 60)
# 步骤1: 上传图片
uploaded_count = upload_images()
if uploaded_count == 0:
print("❌ 没有图片上传,请检查图片目录和配置")
return
# 步骤2: 第一步路径替换
step1_updated = replace_image_links_step1()
if step1_updated == 0:
print("❌ 没有文件被更新,请检查图床URL模式")
return
# 关键提示1: 要求用户修改Typora设置并重启
print("\n" + "=" * 60)
print("⚠️ 重要操作: 请立即完成以下步骤")
print("1. 打开Typora → 偏好设置 → 图像")
print(" - 取消勾选'上传图片'")
print(" - 勾选'优先使用相对路径'")
print("2. 关闭并重启Typora")
print("3. 打开任意文章验证图片显示(应使用绝对路径显示正常)")
print("=" * 60)
input("完成以上操作并验证后,按Enter键继续第二步替换...")
# 步骤3: 第二步路径替换
step2_updated = replace_image_links_step2()
# 最终提示
print("\n" + "=" * 60)
print("✅ 所有操作已完成! 请手动完成以下步骤:")
print("1. 关闭Typora")
print("2. 重新打开Typora")
print("3. 验证图片显示(现在应使用相对路径显示正常)")
print("=" * 60)
# 添加3秒延迟,确保用户看到提示
time.sleep(3)
if __name__ == "__main__":
main()
|