2384 字
12 分钟
告别龟速导出!用Python写一个属于你自己的RAW转JPG神器

告别龟速导出!用Python写一个属于你自己的RAW转JPG神器#

作为一名专业摄影师,你一定对这个场景不陌生:

一天拍摄结束,你将几百上千张 RAW 格式的照片导入电脑。为了快速筛选和分享,你需要将它们全部导出一份 JPG 格式的预览。于是你打开 Adobe Bridge 或 Lightroom,选中所有文件,点击“导出”,然后……进度条开始了它漫长而“优雅”的爬行。电脑风扇狂转,CPU 占用率飙升,而你只能泡杯茶,静静地等待,甚至不敢再操作电脑。

这个过程,我们称之为“生产力黑洞”。Photoshop 和 Bridge 功能强大,但在面对简单、重复的批量转换任务时,它们就像是用一辆重型坦克去送外卖——功能过剩,且效率低下。

但如果我告诉你,有一种方法,可以让这个过程提速数倍甚至十倍以上,并且完全免费、开源呢?

今天,我就来分享如何利用一段简单的 Python 脚本,将你的电脑打造成一台 RAW 格式转换的“性能猛兽”。

为什么脚本会比专业软件更快?#

你可能会疑惑:Adobe 拥有顶尖的工程师团队,他们开发的软件难道还比不过一个几十行代码的脚本?

在“RAW 转 JPG”这个特定任务上,答案是:是的,脚本更快,而且快得多!

原因很简单:

  1. 轻装上阵 vs. 全副武装:Python 脚本只做一件事——解码 RAW 并保存为 JPG。它不会加载庞大的用户界面、数不清的插件、复杂的色彩管理引擎和历史记录功能。它就像一个百米冲刺的运动员,只穿最轻便的跑鞋。
  2. 榨干 CPU 性能:我们这个脚本的核心优势在于真正的并行处理。如果你的电脑有 16 个 CPU 核心,它就能启动 16 个进程,同时转换 16 张照片。而 Ps/Br 复杂的软件架构很难做到如此纯粹、高效的并行。这就像开通了 16 个收费站,车流速度自然飞快!

你的专属“火箭”:三步搞定!#

别被“代码”、“脚本”这些词吓到,整个过程就像组装一个宜家家具一样简单。

第一步:搭建“发射架”(安装环境)#

  1. 安装 Python: 如果你的电脑还没有 Python,请访问 Python 官网 下载并安装最新版本。切记,在安装时勾选 “Add Python to PATH”,这样会方便很多。

  2. 安装核心库: 打开你电脑的终端(Windows 用户请搜索 “CMD” 或 “PowerShell”,Mac 用户使用 “终端” App),然后输入以下命令并回车。这会自动安装我们需要的三个工具库:

    • rawpy: 负责快速读取 RAW 文件。
    • Pillow: 负责处理和保存图像。
    • tqdm: 用来显示一个漂亮的进度条。
    Terminal window
    pip install rawpy Pillow tqdm #win
    pip3 install rawpy Pillow tqdm #macos

第二步:获取“火箭引擎”(脚本代码)#

在你的电脑上创建一个文件夹,比如 D:\MyScripts。在文件夹里新建一个文本文档,将下面的所有代码复制进去,然后将文件名保存为 raw_converter.py (注意后缀是 .py)。

# --- raw_converter.py ---
import os
import sys
import rawpy
from PIL import Image
import multiprocessing
import time
import glob
from tqdm import tqdm
# ===========================================================
# 【核心功能函数】
# ===========================================================
def convert_raw_to_jpg(raw_path, output_dir, quality, input_root_dir, search_subdirs):
"""转换单个 RAW 文件并返回处理结果信息"""
try:
filename = os.path.basename(raw_path)
base_name = os.path.splitext(filename)[0]
if search_subdirs and os.path.dirname(raw_path) != input_root_dir:
relative_dir = os.path.relpath(os.path.dirname(raw_path), input_root_dir)
target_dir = os.path.join(output_dir, relative_dir)
os.makedirs(target_dir, exist_ok=True)
jpg_path = os.path.join(target_dir, f"{base_name}.jpg")
else:
jpg_path = os.path.join(output_dir, f"{base_name}.jpg")
if os.path.exists(jpg_path):
return f"跳过: {filename}"
with rawpy.imread(raw_path) as raw:
rgb_data = raw.postprocess(use_camera_wb=True, output_bps=8, no_auto_bright=True)
pil_image = Image.fromarray(rgb_data)
pil_image.save(jpg_path, 'JPEG', quality=quality, optimize=True)
return f"成功: {filename}"
except Exception as e:
return f"失败: {os.path.basename(raw_path)} - {e}"
def convert_raw_to_jpg_starmap_helper(args):
"""辅助函数"""
return convert_raw_to_jpg(*args)
def batch_convert_raws(input_dir, output_dir, quality, search_subdirs, num_processes):
"""主函数"""
start_time = time.time()
os.makedirs(output_dir, exist_ok=True)
print("\n🚀 正在扫描 RAW 文件...")
raw_extensions = ['*.CR2', '*.CR3', '*.NEF', '*.ARW', '*.DNG', '*.PEF', '*.RAF', '*.RW2', '*.ORF']
raw_files = []
for ext in raw_extensions:
search_pattern = os.path.join(input_dir, '**', ext) if search_subdirs else os.path.join(input_dir, ext)
raw_files.extend(glob.glob(search_pattern, recursive=search_subdirs))
if not raw_files:
print(f"❌ 在目录 '{input_dir}' 中没有找到支持的 RAW 文件。请检查路径或文件类型。")
return
print(f"✅ 共找到 {len(raw_files)} 个 RAW 文件。")
print(f"🔥 开始使用 {num_processes} 个 CPU 核心进行转换...")
tasks = [(file, output_dir, quality, input_dir, search_subdirs) for file in raw_files]
with multiprocessing.Pool(processes=num_processes) as pool:
results = list(tqdm(pool.imap_unordered(convert_raw_to_jpg_starmap_helper, tasks), total=len(tasks), desc="转换进度"))
success_count = sum(1 for r in results if r and r.startswith("成功"))
skipped_count = sum(1 for r in results if r and r.startswith("跳过"))
failed_count = sum(1 for r in results if r and r.startswith("失败"))
end_time = time.time()
print("-" * 50)
print("🎉 转换完成!")
print(f"⏱️ 总耗时: {end_time - start_time:.2f} 秒")
print(f"📊 结果: {success_count} 成功 | {skipped_count} 跳过 | {failed_count} 失败")
# ===========================================================
# 【交互式设置主程序】
# ===========================================================
def get_user_config():
"""通过交互式问答获取用户配置"""
print("=" * 50)
print(" 欢迎使用极速 RAW to JPG 转换脚本!")
print("=" * 50)
# 1. 获取 RAW 文件夹路径
while True:
raw_input_path = input("\n📁 请将你的【RAW 源文件夹】拖到这里,然后按 Enter: ")
clean_path = raw_input_path.strip().strip('"')
if os.path.isdir(clean_path):
input_dir = clean_path
break
else:
print("❌ 错误: 这不是一个有效的文件夹路径,请重试。")
# 2. 获取输出文件夹路径
while True:
raw_output_path = input("\n📂 请将你的【JPG 输出文件夹】拖到这里,然后按 Enter: ")
clean_path = raw_output_path.strip().strip('"')
if clean_path:
output_dir = clean_path
break
else:
print("❌ 错误: 输出路径不能为空,请重试。")
# 3. 获取 JPG 质量
while True:
try:
quality_str = input("\n⚙️ 请输入 JPG 压缩质量 (1-100, 推荐90, 直接按 Enter 使用默认值): ")
if not quality_str:
quality = 90
break
quality = int(quality_str)
if 1 <= quality <= 100:
break
else:
print("❌ 错误: 请输入 1 到 100 之间的数字。")
except ValueError:
print("❌ 错误: 无效输入,请输入一个数字。")
# 4. 【新增】询问是否搜索子文件夹
while True:
subdirs_choice = input("\n🔍 是否需要处理子文件夹中的文件? (y/n, 直接按 Enter 默认为 '是'): ").lower()
if subdirs_choice in ['y', 'yes', '']:
search_subdirs = True
break
elif subdirs_choice in ['n', 'no']:
search_subdirs = False
break
else:
print("❌ 错误: 无效输入,请输入 'y' 或 'n'。")
return input_dir, output_dir, quality, search_subdirs
if __name__ == '__main__':
multiprocessing.freeze_support()
try:
# 获取用户配置(现在会返回4个值)
input_dir, output_dir, quality, search_subdirs = get_user_config()
# 显示最终配置并请求确认
print("\n" + "=" * 50)
print("请确认您的设置:")
print(f" - RAW 来源: {input_dir}")
print(f" - JPG 输出: {output_dir}")
print(f" - JPG 质量: {quality}")
print(f" - 处理子文件夹: {'是' if search_subdirs else '否'}") # <-- 新增确认项
print("=" * 50)
input("✅ 设置无误,请按 Enter 键开始转换...")
# 执行转换
batch_convert_raws(
input_dir=input_dir,
output_dir=output_dir,
quality=quality,
search_subdirs=search_subdirs, # <-- 将用户的选择传递给核心函数
num_processes=multiprocessing.cpu_count()
)
except KeyboardInterrupt:
print("\n\n🛑 操作被用户中断。正在退出...")
sys.exit(0)
except Exception as e:
print(f"\n\n💥 发生意外错误: {e}")
input("\n任务已结束,请按 Enter 键退出。")

第三步:点火发射!#

运行: 再次打开终端(CMD 或 PowerShell),首先使用 cd 命令进入你保存脚本的文件夹,例如:

Terminal window
cd D:\MyScripts #win
cd ~/Documents/Scripts #macos

然后,执行脚本:

Terminal window
python raw_converter.py #win
python3 raw_converter.py #macos

开始对话! 脚本会启动并像一个助手一样向你提问: * 它会让你提供 RAW 源文件夹:你无需手动输入路径,直接用鼠标把文件夹从桌面或文件管理器拖进终端窗口,然后按 Enter! * 它会让你提供 JPG 输出文件夹:同样,拖拽进来,按 Enter! * 它会询问 JPG 质量:输入你想要的数字(比如 95),或者直接按 Enter 使用默认值。 * 它会询问是否处理子文件夹:输入 yn

现在,见证奇迹的时刻到了!你会看到一个实时滚动的进度条,上面显示着处理速度(it/s 就是 张/秒)、完成百分比和预计剩余时间。这个速度,绝对会让你大吃一惊。

正在扫描 RAW 文件...
共找到 1032 个 RAW 文件。
开始使用 16 个进程进行转换...
8%|██████ | 79/1032 [02:07<29:46, 1.87s/it]

这里的 1.87s/it (1.87秒/每项) 可能会让你觉得“一张照片就要快2秒,好慢”。但这是一个巨大的误解!

  • 1.87s/it 的真正含义是:在你的16个CPU核心满负荷工作时,平均一个核心处理完一张照片需要1.87秒。

  • 你真正的处理速度是:因为你有16个核心在同时工作,你的电脑实际的产出速度大约是 16张 / 1.87秒 ≈ 8.5 张/秒 的峰值吞吐能力!

我们换个角度计算:

  • 在 2分07秒 (127秒) 内处理了 79 张照片。

  • 平均每分钟处理 79 / (127/60) ≈ 37.3 张照片。

对于高像素的RAW文件(通常一张在30MB到80MB之间)来说,解码、应用白平衡、然后压缩成高质量JPG,每分钟能稳定处理30-40张,这已经是一个非常高效的速度了。

这个速度会受到以下几个因素影响:

  • RAW文件的大小和像素:你的相机像素越高(例如4500万或6100万像素),文件越大,处理时间就越长。
  • CPU的单核性能:即使核心数相同,新一代的CPU其单个核心的计算能力也更强。
  • 硬盘读写速度 (I/O):如果你的RAW文件和输出目录都在一块慢速的机械硬盘上,硬盘读写可能会成为瓶颈。如果是在固态硬盘 (SSD) 上,则基本不会有影响。

我打包了一键版 点我下载

win用户双击 win.bat 运行 macos用户双击 macos.sh 运行

脚本会自动创建虚拟环境安装依赖运行

告别龟速导出!用Python写一个属于你自己的RAW转JPG神器
https://blog.wlens.top/posts/告别龟速导出用python写一个属于你自己的raw转jpg神器/
作者
Lao Wang
发布于
2025-08-12
许可协议
CC BY-NC-SA 4.0