Python自动化文件批量格式转换工具
·
日常工作中,我们经常需要处理各种格式的文件转换,如图片格式转换、文档格式转换等。手动一个个转换既费时又容易出错。本文将介绍如何使用Python打造一个多功能的批量文件格式转换工具。
功能设计
一个完善的格式转换工具应该支持:
- 图片格式互转(JPG/PNG/WebP/GIF/BMP)
- 文档格式转换(Markdown/HTML/PDF)
- 音频格式转换(MP3/WAV/OGG)
- 视频格式转换(MP4/AVI/MKV)
- 批量处理和进度显示
- 转换选项自定义
核心实现:文件格式转换工具
import os
import sys
import json
import time
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ImageFormat(Enum):
"""支持的图片格式"""
JPEG = ('jpg', 'jpeg')
PNG = ('png',)
WEBP = ('webp',)
GIF = ('gif',)
BMP = ('bmp',)
TIFF = ('tiff', 'tif')
@dataclass
class ConversionTask:
"""转换任务"""
source: Path
target: Path
status: str = 'pending' # pending, processing, completed, failed
error: Optional[str] = None
@dataclass
class ConversionResult:
"""转换结果"""
total: int
success: int
failed: int
skipped: int
duration: float
tasks: List[ConversionTask]
class ImageConverter:
"""图片格式转换器"""
def __init__(self):
self.supported_formats = {fmt.value[0] for fmt in ImageFormat}
def convert_with_pillow(self, source: Path, target: Path, quality: int = 95) -> bool:
"""使用Pillow转换图片"""
try:
from PIL import Image
with Image.open(source) as img:
# 转换RGBA到RGB(JPEG不支持透明通道)
if target.suffix.lower() in ['.jpg', '.jpeg'] and img.mode == 'RGBA':
background = Image.new('RGB', img.size, (255, 255, 255))
background.paste(img, mask=img.split()[3])
img = background
# 保存
save_kwargs = {}
if target.suffix.lower() in ['.jpg', '.jpeg', '.webp']:
save_kwargs['quality'] = quality
elif target.suffix.lower() == '.png':
save_kwargs['optimize'] = True
img.save(target, **save_kwargs)
return True
except Exception as e:
logger.error(f"转换失败 {source}: {e}")
return False
def resize_image(self, source: Path, target: Path, width: int = None,
height: int = None, keep_ratio: bool = True) -> bool:
"""调整图片大小"""
try:
from PIL import Image
with Image.open(source) as img:
original_width, original_height = img.size
if width and height:
new_size = (width, height)
elif width:
if keep_ratio:
ratio = width / original_width
new_size = (width, int(original_height * ratio))
else:
new_size = (width, original_height)
elif height:
if keep_ratio:
ratio = height / original_height
new_size = (int(original_width * ratio), height)
else:
new_size = (original_width, height)
else:
return False
resized = img.resize(new_size, Image.Resampling.LANCZOS)
resized.save(target)
return True
except Exception as e:
logger.error(f"调整大小失败 {source}: {e}")
return False
def create_thumbnail(self, source: Path, target: Path, size: Tuple[int, int] = (200, 200)) -> bool:
"""创建缩略图"""
try:
from PIL import Image
with Image.open(source) as img:
img.thumbnail(size, Image.Resampling.LANCZOS)
img.save(target)
return True
except Exception as e:
logger.error(f"创建缩略图失败 {source}: {e}")
return False
class MarkdownConverter:
"""Markdown转换器"""
def to_html(self, source: Path, target: Path, template: str = None) -> bool:
"""Markdown转HTML"""
try:
import markdown
with open(source, 'r', encoding='utf-8') as f:
md_content = f.read()
html_content = markdown.markdown(
md_content,
extensions=['tables', 'fenced_code', 'codehilite', 'toc']
)
# 默认模板
html_template = template or '''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{title}</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
pre {{ background: #f5f5f5; padding: 15px; overflow-x: auto; }}
code {{ background: #f5f5f5; padding: 2px 5px; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
</style>
</head>
<body>
{content}
</body>
</html>'''
title = source.stem
final_html = html_template.format(title=title, content=html_content)
with open(target, 'w', encoding='utf-8') as f:
f.write(final_html)
return True
except Exception as e:
logger.error(f"Markdown转换失败 {source}: {e}")
return False
def to_pdf(self, source: Path, target: Path) -> bool:
"""Markdown转PDF"""
try:
import markdown
from weasyprint import HTML, CSS
with open(source, 'r', encoding='utf-8') as f:
md_content = f.read()
html_content = markdown.markdown(md_content)
html_string = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{source.stem}</title>
</head>
<body>
{html_content}
</body>
</html>'''
HTML(string=html_string).write_pdf(target)
return True
except ImportError:
logger.error("请安装: pip install weasyprint")
return False
except Exception as e:
logger.error(f"PDF转换失败 {source}: {e}")
return False
class BatchConverter:
"""批量转换管理器"""
def __init__(self):
self.image_converter = ImageConverter()
self.md_converter = MarkdownConverter()
self.tasks: List[ConversionTask] = []
self.results = ConversionResult(0, 0, 0, 0, 0, [])
def add_image_task(self, source: Path, target_format: str, **options):
"""添加图片转换任务"""
if not target_format.startswith('.'):
target_format = '.' + target_format
target = source.with_suffix(target_format)
self.tasks.append(ConversionTask(source, target))
def add_resize_task(self, source: Path, width: int = None, height: int = None):
"""添加图片缩放任务"""
suffix = f"_resized_{width or 'auto'}x{height or 'auto'}"
target = source.parent / f"{source.stem}{suffix}{source.suffix}"
self.tasks.append(ConversionTask(source, target, 'pending'))
def add_thumbnail_task(self, source: Path, size: Tuple[int, int] = (200, 200)):
"""添加缩略图任务"""
target = source.parent / f"{source.stem}_thumb{source.suffix}"
self.tasks.append(ConversionTask(source, target, 'pending'))
def add_markdown_task(self, source: Path, target_format: str):
"""添加Markdown转换任务"""
if target_format == 'html':
target = source.with_suffix('.html')
self.tasks.append(ConversionTask(source, target, 'pending'))
elif target_format == 'pdf':
target = source.with_suffix('.pdf')
self.tasks.append(ConversionTask(source, target, 'pending'))
def scan_directory(self, directory: Path, pattern: str = '*', recursive: bool = False):
"""扫描目录下所有匹配的文件"""
if recursive:
files = directory.rglob(pattern)
else:
files = directory.glob(pattern)
return [f for f in files if f.is_file()]
def execute(self, show_progress: bool = True) -> ConversionResult:
"""执行所有转换任务"""
start_time = time.time()
self.results = ConversionResult(len(self.tasks), 0, 0, 0, 0, self.tasks)
for i, task in enumerate(self.tasks):
if show_progress:
self._print_progress(i, len(self.tasks), task.source.name)
task.status = 'processing'
try:
# 图片转换
if task.source.suffix.lower() in ['.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp', '.tiff']:
success = self.image_converter.convert_with_pillow(
task.source, task.target
)
# Markdown转换
elif task.source.suffix.lower() == '.md':
if task.target.suffix == '.html':
success = self.md_converter.to_html(task.source, task.target)
elif task.target.suffix == '.pdf':
success = self.md_converter.to_pdf(task.source, task.target)
else:
success = False
else:
success = False
task.error = 'Unsupported format'
if success:
task.status = 'completed'
self.results.success += 1
else:
task.status = 'failed'
self.results.failed += 1
except Exception as e:
task.status = 'failed'
task.error = str(e)
self.results.failed += 1
self.results.duration = time.time() - start_time
if show_progress:
self._print_final_result()
return self.results
@staticmethod
def _print_progress(current: int, total: int, filename: str):
"""打印进度"""
percent = (current + 1) / total * 100
bar = '█' * int(percent / 2) + '░' * (50 - int(percent / 2))
print(f"\r[{bar}] {percent:5.1f}% {filename:<30}", end='', flush=True)
def _print_final_result(self):
"""打印最终结果"""
print("\n\n" + "=" * 50)
print("转换完成!")
print(f" ✓ 成功: {self.results.success}")
print(f" ✗ 失败: {self.results.failed}")
print(f" ⏱ 耗时: {self.results.duration:.2f}秒")
print("=" * 50)
# 显示失败的任务
if self.results.failed > 0:
print("\n失败任务:")
for task in self.tasks:
if task.status == 'failed':
print(f" - {task.source.name}: {task.error}")
def main():
converter = BatchConverter()
# 示例1: 批量转换图片格式
print("模式1: 批量图片格式转换")
images = converter.scan_directory(Path('./images'), '*.png')
for img in images:
converter.add_image_task(img, '.jpg')
# 示例2: 创建缩略图
print("\n模式2: 创建缩略图")
images = converter.scan_directory(Path('./images'), '*')
for img in images:
converter.add_thumbnail_task(img, (300, 300))
# 示例3: Markdown转HTML
print("\n模式3: Markdown转HTML")
md_files = converter.scan_directory(Path('./docs'), '*.md')
for md in md_files:
converter.add_markdown_task(md, 'html')
# 执行
results = converter.execute()
# 保存结果
with open('conversion_results.json', 'w') as f:
json.dump({
'total': results.total,
'success': results.success,
'failed': results.failed,
'duration': results.duration
}, f, indent=2)
if __name__ == '__main__':
main()
扩展:视频格式转换
class VideoConverter:
"""视频格式转换器(需要ffmpeg)"""
def convert(self, source: Path, target: Path,
codec: str = None, bitrate: str = None) -> bool:
"""使用ffmpeg转换视频"""
try:
cmd = ['ffmpeg', '-i', str(source)]
if codec:
cmd.extend(['-c:v', codec])
if bitrate:
cmd.extend(['-b:v', bitrate])
cmd.extend(['-y', str(target)]) # -y 覆盖输出
result = subprocess.run(cmd, capture_output=True, timeout=300)
return result.returncode == 0
except Exception as e:
logger.error(f"视频转换失败: {e}")
return False
使用示例
# 图片转WebP
converter = BatchConverter()
for f in Path('./images').glob('*.png'):
converter.add_image_task(f, '.webp')
converter.execute()
# Markdown批量转HTML
converter = BatchConverter()
for f in Path('./docs').glob('**/*.md'):
converter.add_markdown_task(f, 'html')
converter.execute()
总结
通过Python的Pillow库和markdown库,我们可以轻松实现各种文件格式的批量转换。这个工具可以根据实际需求继续扩展更多功能,如添加水印、批量压缩、格式校验等。🙃
更多推荐


所有评论(0)