Repository navigation
Expand file tree
/
Copy pathoffice_converter.py
More file actions
601 lines (513 loc) · 27.1 KB
/
Copy pathoffice_converter.py
File metadata and controls
601 lines (513 loc) · 27.1 KB
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
import os
import argparse
import csv
import win32com.client
import pythoncom
import concurrent.futures
def with_com(func):
"""COM环境初始化装饰器,用于多线程环境下的Office COM调用"""
def wrapper(*args, **kwargs):
files = args[0] if args else kwargs.get('files', [])
if not files:
return []
# 初始化当前线程的COM环境
pythoncom.CoInitialize()
try:
return func(*args, **kwargs)
finally:
# 释放当前线程的COM环境
pythoncom.CoUninitialize()
return wrapper
def parallel_convert(convert_func, files, delete_old, max_workers):
"""
多线程分块执行转换函数
参数:
convert_func: 要执行的转换函数(如 convert_word_files)
files: 需要转换的文件路径列表
delete_old: 转换成功后是否删除原文件
max_workers: 最大并发线程数
返回:
tuple: (all_converted, all_repaired)
- all_converted: 转换成功的文件路径元组列表 [(旧路径, 新路径), ...]
- all_repaired: 触发了自动修复的旧文件路径列表 [旧路径1, 旧路径2, ...]
"""
if not files:
return [], []
# 不要启动比文件数更多的线程
workers = min(max_workers, len(files))
if workers <= 0:
return [], []
# 将文件列表分块
chunk_size = (len(files) + workers - 1) // workers
chunks = [files[i:i + chunk_size] for i in range(0, len(files), chunk_size)]
all_converted = []
all_repaired = []
# 使用线程池并发执行转换
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
futures = [executor.submit(convert_func, chunk, delete_old) for chunk in chunks]
for future in concurrent.futures.as_completed(futures):
try:
# 每个线程现在返回 (converted_list, repaired_list)
converted, repaired = future.result()
if converted:
all_converted.extend(converted)
if repaired:
all_repaired.extend(repaired)
except Exception as e:
print(f"多线程转换执行出错: {e}")
return all_converted, all_repaired
def get_files_to_convert(target_dir, recursive=False):
"""
获取目录下所有需要转换的旧版Office文件
支持递归搜索子目录
"""
# 初始化分别存储三种格式文件的列表
doc_files, xls_files, ppt_files = [], [], []
# 判断是否需要递归遍历目录
if recursive:
# 递归遍历目录树,root为当前目录,dirs为子目录,files为文件名
for root, dirs, files in os.walk(target_dir):
for file in files:
# 忽略Office产生的临时隐藏文件(通常以~$开头)
if file.startswith('~$'):
continue
# 获取文件的绝对路径,COM接口通常要求使用绝对路径
abs_path = os.path.abspath(os.path.join(root, file))
# 获取文件的扩展名并转为小写
ext = os.path.splitext(file)[1].lower()
# 根据扩展名将文件分类存入不同的列表,准备后续转换
if ext == '.doc':
doc_files.append(abs_path)
elif ext == '.xls':
xls_files.append(abs_path)
elif ext == '.ppt':
ppt_files.append(abs_path)
else:
# 仅遍历当前目录,不进入子目录
for file in os.listdir(target_dir):
# 同样忽略以~$开头的临时文件
if file.startswith('~$'):
continue
# 拼接绝对路径并检查是否为文件而不是目录
abs_path = os.path.abspath(os.path.join(target_dir, file))
if os.path.isfile(abs_path):
# 提取扩展名进行小写处理
ext = os.path.splitext(file)[1].lower()
# 分类收集文件路径,方便后续按类型批量处理
if ext == '.doc':
doc_files.append(abs_path)
elif ext == '.xls':
xls_files.append(abs_path)
elif ext == '.ppt':
ppt_files.append(abs_path)
# 返回分类好的文件列表元组,供外部调用
return doc_files, xls_files, ppt_files
@with_com
def convert_word_files(files, delete_old=False):
"""批量转换Word文档 (doc -> docx)"""
# 如果没有需要转换的Word文件,则直接返回
if not files:
return [], []
print(f"线程开始转换 {len(files)} 个 Word 文件...")
# 尝试启动Word应用程序,如果未安装Word则会抛出异常
try:
# 启动Word应用程序,使用DispatchEx可以启动独立进程
word = win32com.client.DispatchEx("Word.Application")
except Exception as e:
# 捕获启动异常,提示用户并中止Word文件的转换
print(f"错误: 无法启动 Word 应用程序,请检查是否已正确安装 Microsoft Word。\n详细错误: {e}")
return [], []
# 设置为不可见,在后台静默运行以提高速度
word.Visible = False
# 记录成功转换的文件路径,用于CSV更新
converted_files = []
repaired_files = []
try:
# 遍历所有收集到的doc文件路径
for file_path in files:
is_repaired = False
# 生成新文件的路径,将后缀替换为.docx
new_path = os.path.splitext(file_path)[0] + '.docx'
# 如果新格式文件已经存在,则直接记录为已转换并跳过
if os.path.exists(new_path):
print(f"跳过: {new_path} 已存在")
converted_files.append((file_path, new_path))
# 如果用户要求删除且新文件已存在,可以选择删除旧文件(需谨慎,这里按照“如果新文件已存在,也删除旧文件”逻辑处理)
if delete_old and os.path.exists(file_path):
try:
os.remove(file_path)
print(f"已删除旧文件: {file_path}")
except Exception as e:
print(f"删除旧文件失败: {file_path}, 错误: {e}")
continue
print(f"转换: {file_path} -> {new_path}")
# 使用内层 try-except 包裹单个文件的转换逻辑
# 防止单个文件损坏导致整个批次的转换中断
try:
# 尝试正常打开
doc = word.Documents.Open(file_path, ReadOnly=True)
except Exception as e:
print(f" 警告: 正常打开失败,尝试执行受损修复模式... ({e})")
try:
# 如果打开失败,使用 OpenAndRepair=True 参数尝试自动修复
doc = word.Documents.Open(file_path, ReadOnly=True, OpenAndRepair=True)
is_repaired = True
repaired_files.append(file_path)
except Exception as repair_e:
print(f" 错误: 修复并打开 Word 文件失败: {file_path}。跳过此文件。详细错误: {repair_e}")
continue
try:
# 16 代表 wdFormatDocumentDefault 或 wdFormatXMLDocument (docx格式)
doc.SaveAs(new_path, FileFormat=16)
except Exception as save_e:
print(f" 错误: 保存为 docx 时出错: {file_path}。跳过此文件。详细错误: {save_e}")
doc.Close()
continue
finally:
# 保存后确保关闭当前文档
try:
doc.Close()
except:
pass
# 尝试重新读取转换后的文件以确保成功
try:
test_doc = word.Documents.Open(new_path, ReadOnly=True)
test_doc.Close()
converted_files.append((file_path, new_path))
except Exception as verify_e:
print(f" 错误: 验证转换后的新文件失败: {new_path}。可能是转换不完整。详细错误: {verify_e}")
# 如果验证失败,不应该将其视为转换成功
continue
# 如果要求删除旧文件,在转换并验证通过后执行删除
# 但如果触发了自动修复,无论用户是否指定 -d 都不删除旧文件
if delete_old and (file_path, new_path) in converted_files and not is_repaired:
try:
os.remove(file_path)
print(f"已删除旧文件: {file_path}")
except Exception as e:
print(f"删除旧文件失败: {file_path}, 错误: {e}")
elif delete_old and is_repaired:
print(f"保留了旧文件: {file_path} (因触发了受损修复,为安全起见不予删除)")
except Exception as e:
# 捕获并打印转换过程中的异常,防止程序崩溃
print(f"Word线程严重出错: {e}")
finally:
# 无论是否发生异常,确保退出Word进程,释放系统资源
word.Quit()
return converted_files, repaired_files
@with_com
def convert_excel_files(files, delete_old=False):
"""批量转换Excel工作簿 (xls -> xlsx)"""
# 如果列表为空则无需执行任何操作
if not files:
return [], []
print(f"线程开始转换 {len(files)} 个 Excel 文件...")
# 尝试启动Excel应用程序
try:
# 启动Excel应用程序独立进程
excel = win32com.client.DispatchEx("Excel.Application")
except Exception as e:
# 捕获启动异常,提示用户并中止Excel文件的转换
print(f"错误: 无法启动 Excel 应用程序,请检查是否已正确安装 Microsoft Excel。\n详细错误: {e}")
return [], []
# 后台静默运行
excel.Visible = False
# 关闭警告提示,避免覆盖文件时弹窗阻塞自动化程序
excel.DisplayAlerts = False
# 记录成功转换的文件路径,用于CSV更新
converted_files = []
repaired_files = []
try:
# 逐个处理xls文件
for file_path in files:
is_repaired = False
# 生成新的xlsx文件路径
new_path = os.path.splitext(file_path)[0] + '.xlsx'
# 检查文件是否已存在
if os.path.exists(new_path):
print(f"跳过: {new_path} 已存在")
converted_files.append((file_path, new_path))
if delete_old and os.path.exists(file_path):
try:
os.remove(file_path)
print(f"已删除旧文件: {file_path}")
except Exception as e:
print(f"删除旧文件失败: {file_path}, 错误: {e}")
continue
print(f"转换: {file_path} -> {new_path}")
try:
# 尝试正常打开旧版xls工作簿,禁用宏更新(UpdateLinks=0)
wb = excel.Workbooks.Open(file_path, UpdateLinks=0, ReadOnly=True)
except Exception as e:
print(f" 警告: 正常打开失败,尝试执行受损修复模式... ({e})")
try:
# 如果打开失败,使用 CorruptLoad=2 参数尝试自动修复并打开 (xlRepairFile)
# xlRepairFile = 2, xlExtractData = 3
wb = excel.Workbooks.Open(file_path, UpdateLinks=0, ReadOnly=True, CorruptLoad=2)
is_repaired = True
repaired_files.append(file_path)
except Exception as repair_e:
print(f" 错误: 修复并打开 Excel 文件失败: {file_path}。跳过此文件。详细错误: {repair_e}")
continue
try:
# 51 代表 xlOpenXMLWorkbook (xlsx格式)
wb.SaveAs(new_path, FileFormat=51)
except Exception as save_e:
print(f" 错误: 保存为 xlsx 时出错: {file_path}。跳过此文件。详细错误: {save_e}")
wb.Close(SaveChanges=False)
continue
finally:
# 关闭工作簿,不保存原文件的修改
try:
wb.Close(SaveChanges=False)
except:
pass
# 尝试重新读取转换后的文件以确保成功
try:
test_wb = excel.Workbooks.Open(new_path, UpdateLinks=0, ReadOnly=True)
test_wb.Close(SaveChanges=False)
converted_files.append((file_path, new_path))
except Exception as verify_e:
print(f" 错误: 验证转换后的新文件失败: {new_path}。可能是转换不完整。详细错误: {verify_e}")
continue
# 如果要求删除旧文件,在转换并验证通过后执行删除
# 但如果触发了自动修复,无论用户是否指定 -d 都不删除旧文件
if delete_old and (file_path, new_path) in converted_files and not is_repaired:
try:
os.remove(file_path)
print(f"已删除旧文件: {file_path}")
except Exception as e:
print(f"删除旧文件失败: {file_path}, 错误: {e}")
elif delete_old and is_repaired:
print(f"保留了旧文件: {file_path} (因触发了受损修复,为安全起见不予删除)")
except Exception as e:
# 打印Excel转换过程中的错误信息
print(f"Excel线程严重出错: {e}")
finally:
# 确保退出Excel进程,防止后台残留Excel实例
excel.Quit()
return converted_files, repaired_files
@with_com
def convert_ppt_files(files, delete_old=False):
"""批量转换PowerPoint演示文稿 (ppt -> pptx)"""
# 无需转换时直接退出
if not files:
return [], []
print(f"线程开始转换 {len(files)} 个 PowerPoint 文件...")
# 尝试启动PowerPoint应用程序
try:
# 启动PowerPoint应用程序
ppt = win32com.client.DispatchEx("PowerPoint.Application")
except Exception as e:
# 捕获启动异常,提示用户并中止PowerPoint文件的转换
print(f"错误: 无法启动 PowerPoint 应用程序,请检查是否已正确安装 Microsoft PowerPoint。\n详细错误: {e}")
return [], []
# 注意:某些版本的PowerPoint可能不支持完全隐藏窗口
# 此处依赖Open方法中的WithWindow参数来控制显示
# 记录成功转换的文件路径,用于CSV更新
converted_files = []
repaired_files = []
try:
# 遍历需要转换的ppt文件列表
for file_path in files:
is_repaired = False
# 构造新的pptx文件路径
new_path = os.path.splitext(file_path)[0] + '.pptx'
# 跳过已转换的文件
if os.path.exists(new_path):
print(f"跳过: {new_path} 已存在")
converted_files.append((file_path, new_path))
if delete_old and os.path.exists(file_path):
try:
os.remove(file_path)
print(f"已删除旧文件: {file_path}")
except Exception as e:
print(f"删除旧文件失败: {file_path}, 错误: {e}")
continue
print(f"转换: {file_path} -> {new_path}")
try:
# 打开旧版ppt文件,WithWindow=False尽量不显示窗口
# 这样可以防止弹窗打扰用户
# PowerPoint COM 没有类似于 Word 的 OpenAndRepair 参数
# 如果遇到错误通常代表文件严重损坏,无法处理
presentation = ppt.Presentations.Open(file_path, ReadOnly=True, WithWindow=False)
except Exception as open_e:
print(f" 错误: 打开 PowerPoint 文件失败: {file_path}。文件可能严重损坏。跳过此文件。详细错误: {open_e}")
# 由于无法修复,直接跳过处理,不进行后续保存等操作
continue
try:
# 24 代表 ppSaveAsOpenXMLPresentation (pptx格式)
presentation.SaveAs(new_path, FileFormat=24)
except Exception as save_e:
print(f" 错误: 保存为 pptx 时出错: {file_path}。跳过此文件。详细错误: {save_e}")
presentation.Close()
continue
finally:
# 转换完成后(或失败后)关闭该演示文稿
try:
presentation.Close()
except:
pass
# 尝试重新读取转换后的文件以确保成功
try:
test_presentation = ppt.Presentations.Open(new_path, ReadOnly=True, WithWindow=False)
test_presentation.Close()
converted_files.append((file_path, new_path))
except Exception as verify_e:
print(f" 错误: 验证转换后的新文件失败: {new_path}。可能是转换不完整。详细错误: {verify_e}")
continue
# 如果要求删除旧文件,在转换并验证通过后执行删除
# 但如果触发了自动修复或读取错误,无论用户是否指定 -d 都不删除旧文件
if delete_old and (file_path, new_path) in converted_files and not is_repaired:
try:
os.remove(file_path)
print(f"已删除旧文件: {file_path}")
except Exception as e:
print(f"删除旧文件失败: {file_path}, 错误: {e}")
elif delete_old and is_repaired:
print(f"保留了旧文件: {file_path} (因触发了受损修复/读取错误,为安全起见不予删除)")
except Exception as e:
# 记录PowerPoint转换时的异常情况
print(f"PowerPoint线程严重出错: {e}")
finally:
# 确保关闭PowerPoint进程,释放系统内存
ppt.Quit()
return converted_files, repaired_files
def process_csv_file(csv_path, delete_old=False, max_workers=5):
"""
读取CSV文件,过滤需要转换的旧版Office文件(根据新后缀判断),并将结果更新回CSV文件
"""
print(f"正在读取 CSV 文件: {csv_path}")
# 用于存储所有CSV行
rows = []
# 用于收集需要转换的文件路径
doc_files, xls_files, ppt_files = [], [], []
# 建立路径到行索引的映射,以便转换后快速更新
path_to_row_index = {}
# 读取CSV文件
try:
# 使用 utf-8-sig 处理可能的 BOM 头部,防止首列名解析错误
with open(csv_path, 'r', encoding='utf-8-sig', newline='') as f:
reader = csv.DictReader(f)
fieldnames = reader.fieldnames
for i, row in enumerate(reader):
rows.append(row)
# 获取该文件的新后缀并转为小写进行判断
ext = row.get('新后缀', '').lower()
# 仅处理扩展名为旧版 Office 格式的记录
if ext in ['.doc', '.xls', '.ppt']:
# 拼接完整的文件路径 (使用“文件新名”因为原文件可能已经被重命名)
dir_path = row.get('所在目录', '')
file_name = row.get('文件新名', '')
if dir_path and file_name:
# 确保目录路径末尾有正确的斜杠,避免 os.path.join 拼接错误
full_path = os.path.abspath(os.path.join(dir_path, file_name))
# 如果文件存在,则加入待转换列表
if os.path.exists(full_path):
# 记录该文件在列表中的索引
path_to_row_index[full_path] = i
if ext == '.doc':
doc_files.append(full_path)
elif ext == '.xls':
xls_files.append(full_path)
elif ext == '.ppt':
ppt_files.append(full_path)
except Exception as e:
print(f"读取 CSV 文件失败: {e}")
return
total_files = len(doc_files) + len(xls_files) + len(ppt_files)
if total_files == 0:
print("CSV中未找到待转换的旧版 Office 文件 (.doc, .xls, .ppt)。")
return
print(f"共找到 {total_files} 个文件待转换。将使用 {max_workers} 个并发线程进行处理。")
# 执行转换,并收集转换成功的文件路径元组 (原路径, 新路径) 和受损修复的文件路径
converted_docs, repaired_docs = parallel_convert(convert_word_files, doc_files, delete_old, max_workers)
converted_xls, repaired_xls = parallel_convert(convert_excel_files, xls_files, delete_old, max_workers)
converted_ppts, repaired_ppts = parallel_convert(convert_ppt_files, ppt_files, delete_old, max_workers)
all_converted = converted_docs + converted_xls + converted_ppts
all_repaired = repaired_docs + repaired_xls + repaired_ppts
if all_repaired:
print(f"\n[重要提示] 共有 {len(all_repaired)} 个文件触发了受损自动修复/读取警告(已强制保留旧版文件):")
for path in all_repaired:
print(f" - {path}")
if not all_converted:
print("\n没有文件被成功转换并验证通过,CSV文件未更新。")
return
print(f"\n正在更新 CSV 文件,共 {len(all_converted)} 条记录...")
# 更新CSV行数据
for old_path, new_path in all_converted:
if old_path in path_to_row_index:
row_idx = path_to_row_index[old_path]
# 获取新文件名和新后缀
new_filename = os.path.basename(new_path)
new_ext = os.path.splitext(new_filename)[1]
# 更新字段
rows[row_idx]['文件新名'] = new_filename
rows[row_idx]['新后缀'] = new_ext
rows[row_idx]['识别器'] = 'zip_office' # 转换后变成了基于zip的OpenXML格式
# 将更新后的数据写回CSV文件
try:
with open(csv_path, 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f"CSV 文件已成功更新: {csv_path}")
except Exception as e:
print(f"写入 CSV 文件失败: {e}")
def main():
"""主程序入口,解析命令行参数并执行转换逻辑"""
# 配置命令行参数解析器,添加描述信息
parser = argparse.ArgumentParser(description="批量将旧版Office文件(doc, xls, ppt)转换为新版(docx, xlsx, pptx)")
# 添加必须的可选参数 -p 或 --path,用于指定要转换的目录路径
parser.add_argument("-p", "--path", help="要转换的文件目录路径(与-i二选一)")
# 添加可选参数 -i 或 --input,用于指定CSV文件
parser.add_argument("-i", "--input", help="包含文件信息的CSV文件路径(与-p二选一)")
# 添加可选参数 -r 或 --recursive,用于指定是否递归
# action="store_true" 意味着如果命令行中带有此参数,则值为 True
parser.add_argument("-r", "--recursive", action="store_true", help="是否递归处理子目录")
# 添加可选参数 -d 或 --delete,用于指定是否在转换成功后删除旧文件,默认保留
parser.add_argument("-d", "--delete", action="store_true", help="转换成功后删除旧版Office文件(默认保留)")
# 添加可选参数 -w 或 --workers,用于指定多线程并发数
parser.add_argument("-w", "--workers", type=int, default=5, help="并发转换的线程数(默认:5)")
# 解析用户输入的命令行参数
args = parser.parse_args()
# 检查必须提供 -p 或 -i 之一
if not args.path and not args.input:
parser.error("必须指定 -p/--path 或 -i/--input 参数")
if args.input:
# 如果指定了 -i 参数,则走CSV处理流程
if not os.path.isfile(args.input):
print(f"错误: CSV文件 '{args.input}' 不存在")
return
process_csv_file(args.input, args.delete, args.workers)
else:
# 否则走原本的目录扫描流程
# 检查指定的目录路径是否存在,如果不存在则提示错误并退出
if not os.path.isdir(args.path):
print(f"错误: 目录 '{args.path}' 不存在")
return
print(f"正在扫描目录: {args.path} (递归: {args.recursive})")
# 调用函数获取所有需要转换的文件列表
doc_files, xls_files, ppt_files = get_files_to_convert(args.path, args.recursive)
# 统计找到的文件总数,如果为0则提前结束
total_files = len(doc_files) + len(xls_files) + len(ppt_files)
if total_files == 0:
print("未找到需要转换的旧版Office文件 (.doc, .xls, .ppt)")
return
print(f"共找到 {total_files} 个文件待转换。将使用 {args.workers} 个并发线程进行处理。")
# 依次调用各个格式的转换函数
# 将相同格式的文件集中处理,避免频繁启动和关闭Office进程
# 并正确解包返回值
converted_docs, repaired_docs = parallel_convert(convert_word_files, doc_files, args.delete, args.workers)
converted_xls, repaired_xls = parallel_convert(convert_excel_files, xls_files, args.delete, args.workers)
converted_ppts, repaired_ppts = parallel_convert(convert_ppt_files, ppt_files, args.delete, args.workers)
all_repaired = repaired_docs + repaired_xls + repaired_ppts
if all_repaired:
print(f"\n[重要提示] 共有 {len(all_repaired)} 个文件触发了受损自动修复/读取警告(已强制保留旧版文件):")
for path in all_repaired:
print(f" - {path}")
# 提示所有任务结束
print("\n所有转换任务已完成!")
if __name__ == "__main__":
# 确保当前脚本是作为主程序运行而不是被其他模块导入
main()