我电脑里躺着几千张截图名字不是“截屏2024-06-13 15.22.31”就是“Screenshot 2024-06-13 at 15.22.31”想找一张三个月前的报错截图基本靠缘分。Screenshotify就是冲这个痛点去的——用AI解析截图内容自动把文件名改成一句可读的语义描述比如把截屏2024-06-13 15.22.31.png重命名为cloudflare_error_502_dashboard_20240613-152231.png。我把它按 Show HN 的形式开源出来目前这个工具的核心逻辑和实现已经跑通。这篇东西就把完整思路、技术选型、踩坑过程和落地代码都摊开聊适合正在做 AI 应用开发、图片批处理、以及想给本地工作流加点 AI 能力的朋友参考。1. 为什么截图重命名值得用AI重做一遍1.1 截图管理的真实痛点比你想的更严重很多人觉得自己不会在截图堆里找不到东西我实测下来的结论是你会而且比你想象的频繁。截图文件名通常是系统自动生成的乱码编号里面没有任何语义信息你只能靠“大概记得是哪天截的”去翻文件夹一旦超过两百张翻找的挫败感直接劝退。问题本质不是“文件多”而是“文件名没承载信息”。一张截图的价值在于它记录的内容——是一段报错、一个订单号、一段聊天记录还是一个设计参考。文件名本应是内容的索引但系统只能给你时间戳和随机数字。我统计过自己一个月的截图量平均每天 12 张一个月就是 360 张一年下来四千多张文件名全是image_001.png、截屏2024-06-13 15.22.31.png这种。等你想找某张图时只能一张张打开预览纯粹是手工体力活。1.2 批量重命名工具解决不了“语义”问题传统的批量重命名工具bulk rename utility很多功能也强支持正则替换、序号填充、时间戳格式转换。但它们都有一个前提你得先知道自己想要什么规则。比如把IMG_001.jpg改成2024_06_IMG_001.jpg规则是死的不会去理解图里是什么。这就像图书馆光给书编了个流水号却没按内容分类上架。你问管理员“有没有讲数据库的书”他只能回你“编号1743”。批量重命名工具解决的是“格式统一”不解决“内容可检索”。AI 的价值就在于它能看懂图里的文字和结构然后把“内容概括”写进文件名这才是真正从根上解决找图难的问题。1.3 Screenshotify 的设计定位Screenshotify 做了三件别的工具没做全的事全自动监听截图目录新截图落地后自动触发重命名不用手动拖拽。先 OCR 提取图中文字再交给大模型生成一个适合做文件名的短标题。保留原始时间信息按语义标题_日期_序号的结构命名排序和检索都方便。设计上我坚持本地优先。截图内容往往比想象的敏感支付记录、聊天内容、后台地址一旦上传第三方服务就多一层泄露风险。所以这个工具默认全部本地运行OCR 模型本地跑大模型也优先接本地部署的方案后面我会详细说选型。2. 技术选型OCR和视觉模型怎么做决策2.1 整个处理链路拆解Screenshotify 的处理流程不复杂但每一步都有讲究监听系统截图保存目录识别新文件。读取图片做必要的预处理缩放、灰度化、降噪。用 OCR 把图中的文字提取出来。把 OCR 文本可选的图片摘要喂给大模型生成符合命名规范的标题。对标题做字符清洗处理冲突执行重命名。这五步里第 3 步和第 4 步是技术选型的关键。OCR 负责“读”大模型负责“概括”。前者是数据管道后者是语义引擎哪个拉胯都不行。2.2 OCR 选型Tesseract、PaddleOCR、EasyOCR 怎么选OCR 部分我试过三个方案各有脾气。Tesseract 是老牌开源方案优点是完全离线安装简单一个pip install pytesseract就能跑。缺点也明显对中文支持一般对界面截图的排版混乱场景经常识别出大段乱序文字。比如一个注册页面Tesseract 可能把按钮文字和输入框提示混在一起顺序还不对。EasyOCR 用深度学习模型识别准确率比 Tesseract 高一个档次特别是英文和印刷体。但依赖 PyTorch打包体积和内存开销都不小在配置一般的机器上首次加载模型要等好几秒体验一般。PaddleOCR 是百度开源的一套工具中文识别能力在开源方案里属于第一梯队而且提供了轻量模型CPU 上也能跑实测下来对界面截图的识别效果最好。缺点是依赖安装稍微复杂一点Paddle 的 Python 包有时候会和现有环境产生依赖冲突。我最终选了 PaddleOCR 的轻量模型。原因很简单截图场景中文占比很高准确率优先。Tesseract 识别中文经常出现漏字误导大模型的程度比不识别还糟糕——因为它会一本正经地生成一个错得离谱的文件名。2.3 命名生成传统规则和LLM的差距第一次做这个工具时我试着不走大模型纯用规则从 OCR 文本里截取前几个字当文件名。比如 OCR 识别出“支付成功 订单号 202406123456”就取“支付成功_20240612_153022.png”。结果效果很差。原因是 OCR 输出的文本顺序不稳定而且界面里常常混着导航栏、广告、推荐内容直接截前几个字经常跑偏。截一个新闻页面能取到“首页 登录 注册”这种噪音内容完全不可用。换用大模型之后质量直接上一个台阶。给它 OCR 文本让它从中提取核心主题再缩成 3-8 个单词的文件名它能自动忽略广告和导航噪音精准抓住页面主体。比如一个报错页面OCR 里有502 Bad Gateway、nginx/1.18.0、联系管理员一大堆模型最终生成nginx_502_error_page。这就是传统规则和 LLM 的本质区别规则只能做机械截取模型能理解主次。2.4 大模型部署本地小模型还是云端API大模型部分有两个路线本地私有部署或者调用云端 API。本地部署选的模型要兼顾体积和效果。我实测过几款适合终端设备的小型模型7B 级别以下的速度和效果比较均衡。用量化版本后内存占用可以压到 5-6 GBCPU 上跑也能接受——生成一个标题大概 1-3 秒。优点是零隐私泄露风险所有处理不离开电脑而且没有 API 费用。云端 API 的好处是模型大、理解能力强特别是图片里信息密度高的时候总结更准确。坏处有三一是截图内容会传到第三方服务器存不存储全看对方安全条款二是每次调用都有成本和延迟截图一多就肉疼三是依赖网络离线环境直接瘫痪。我推荐的做法是做一个兼容层默认走本地模型如果需要更强理解能力可以切到 API 模式。“本地为主、云端可选”这个模式也符合很多 AI 应用落地的实际情况。3. 命名规范设计好文件名不是随便生成的3.1 文件名结构怎么定义才合理命名规范是整个工具的地基。建模规范没定好后面所有功能都是空中楼阁。我最终定的结构是{内容主标题}_{yyyyMMdd-HHmmss}_{序号}.png比如git_merge_conflict_error_20240613-152231_01.png这个结构有三个讲究第一语义标题放最前面。你在文件管理器里按名称排序时所有语义相近的截图会聚在一起比如所有cloudflare_error_*的会排成一列找起来很直观。第二时间戳用yyyyMMdd-HHmmss格式不要用2024-06-13 15.22.31这种带空格带冒号的。因为冒号和空格在某些命令行工具和脚本里需要转义处理起来很麻烦而且文件名本身也不适合太长。第三末尾保留两位序号。如果同一天内两张截图内容完全一样模型生成的标题也一样序号能保证文件名唯一不会覆盖。3.2 提示词工程如何让模型乖乖输出文件名这个环节非常关键模型不约束就自由发挥可能给你生成一长串带空格的句子。我用的提示词框架大概是这样的你是一个文件命名助手。请根据下面OCR文本生成一个适合用作文件名的简短标题。 要求 - 全部使用小写英文字母和数字以及下划线 - 不要包含空格、引号、冒号、斜杠等特殊符号 - 长度控制在3到8个单词之间 - 概括图片的核心内容忽略导航、广告等干扰信息 - 只输出标题本身不要多余的解释有几个细节值得琢磨指定“小写下划线”是给跨平台兼容铺路。Linux 区分大小写macOS 默认不区分如果文件名里大小写乱用后面在别的设备上容易混淆。明确告诉模型“忽略导航、广告”能大幅减少标题跑偏。“只输出标题本身”很重要模型不加这一句会给你输出解释比如“好的根据您的要求建议命名为...”。解释文本混进文件名就全毁了。3.3 字符清洗最后一层保险即使提示词写得再清楚模型也有概率输出不合法字符。所以我加了清洗函数做兜底把中文字符全部转成拼音或英文关键词这一步可以在提示词层面解决但清洗函数里也要做映射。替换所有非[a-z0-9_]为下划线。连续多个下划线压缩成一个。去掉首尾的下划线。最终文件名长度超过 80 字符时截断。这个清洗逻辑能在模型出错时保住文件名的合法性。我还设过一个保险如果清洗后文件名变成空字符串就用screenshot_未识别_时间戳.png兜底保证文件不会被跳过。4. 实操全记录从零搭建Screenshotify4.1 环境准备和项目骨架整个工具我用 Python 实现依赖清单如下watchdog监听文件系统目录变化。paddleocrOCR识别。openai对接大模型兼容本地 Ollama 的 API 格式。Pillow图片预处理。python-dotenv管理环境变量。安装命令pip install watchdog paddleocr openai pillow python-dotenvPaddleOCR 首次运行会自动下载模型文件到本地缓存目录大概 10-50 MB取决于你选的模型版本。这一步在国内网络环境下偶尔会卡建议提前手动下载并放到~/.paddleocr/下。项目结构保持简单screenshotify/ ├── main.py # 入口启动监听 ├── config.py # 配置项 ├── ocr_engine.py # OCR封装 ├── namer.py # LLM命名生成 ├── rename.py # 重命名逻辑 └── clean.py # 文件名清洗4.2 目录监听实现用 watchdog 监听系统截图目录核心代码如下import time from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class ScreenshotHandler(FileSystemEventHandler): def on_created(self, event): if event.is_directory: return path event.src_path if not path.lower().endswith((.png, .jpg, .jpeg)): return # 防抖等文件写入完成 time.sleep(1.5) process_screenshot(path) def start_watching(target_dir): handler ScreenshotHandler() observer Observer() observer.schedule(handler, target_dir, recursiveFalse) observer.start() print(f监听中{target_dir}) observer.join()这里有个小坑macOS 的截屏保存机制是先写临时文件再重命名on_created事件触发的路径经常是临时的文件还没写完就读取容易出错。我的解决办法是加 1.5 秒的延迟等待文件写入完成再处理。如果文件比较大这个延迟可以适当调大。Windows 的截图工具行为不一样有些截图工具是直接写文件不经过临时路径监听机制稳定一些。Linux 上则取决于你用的桌面环境和截图工具GNOME 的gnome-screenshot和 Spectacle 表现都还行。4.3 OCR识别与图片预处理PaddleOCR 的封装方式如下from paddleocr import PaddleOCR ocr PaddleOCR( use_angle_clsFalse, langch, show_logFalse, use_gpuFalse, # CPU模式 ) def extract_text(image_path): result ocr.ocr(image_path, clsFalse) if not result: return lines [] for page in result: for item in page: # item [框坐标, (识别文本, 置信度)] text item[1][0] conf item[1][1] if conf 0.5: # 低于置信度的不要 lines.append(text) return \n.join(lines)预处理是整个流程里容易被低估的环节。我踩过两个坑一是超大截图。现在很多人的屏幕是 4K 分辨率一张截图可能有 4000×2250 像素。PaddleOCR 处理这个分辨率很慢而且不一定准确。我实测把图片缩放到宽度 1280 像素后识别速度快了三倍准确率反而没有下降。二是暗色模式。界面截图经常是深色背景、白色文字PaddleOCR 对这类图片的识别效果稍差。我会在前处理里做一次灰度化和对比度增强效果提升明显。4.4 LLM生成文件名兼容层设计import os from openai import OpenAI # 支持本地Ollama和云端API client OpenAI( base_urlos.getenv(LLM_BASE_URL, http://localhost:11434/v1), api_keyos.getenv(LLM_API_KEY, ollama), ) def generate_title(ocr_text, model: str qwen2.5:7b): messages [ {role: system, content: 你是一个文件命名助手只输出标题本身。}, {role: user, content: f根据OCR内容生成文件名\n{ocr_text}} ] resp client.chat.completions.create( modelmodel, messagesmessages, temperature0.3, max_tokens30, ) raw resp.choices[0].message.content.strip() return clean_filename(raw)这里我用的模型是qwen2.5:7b的量化版在 Ollama 里一条命令就能拉起来ollama pull qwen2.5:7btemperature设为 0.3让输出稳定一些不要每次生成不一样的名字。这个参数我调过很多次温度太高模型会给同一张图生成完全不同的标题不适合文件命名场景。4.5 重命名与文件系统边界重命名的核心逻辑import os import shutil from pathlib import Path def safe_rename(src_path, dest_path): try: os.rename(src_path, dest_path) except OSError as e: if e.errno 18: # EXDEV: Cross-device link not permitted # 跨设备改用复制删除 shutil.copy2(src_path, dest_path) os.remove(src_path) else: raise原来的我直接用os.rename直到用户反馈在某些目录上会报OSError: [Errno 18] EXDEV: Cross-device link not permitted。查了一下问题核心是rename系统调用不能跨文件系统——比如源文件在/home分区目标目录在/tmp分区这实际上是不同的挂载点内核不允许这种重命名。解决办法就是用shutil.copy2复制过去再删除源文件代价是磁铁和磁盘开销变大了但能保证功能不挂。这部分在容器环境特别容易踩到。热词里出现 “resolve launch spec failed: exdev: cross-device link not permitted” 大概也是这个场景开发工具里卷和宿主机目录跨了设备各种软件都会受影响。Screenshotify 在 Docker 里跑的时候同样会遇到因为挂载卷和目标目录跨了文件系统。4.6 冲突检测与序号追加同名文件的问题不能忽略同一张截图生成一样标题的概率远比你想象的高。我的策略是def resolve_conflict(dest_path): if not os.path.exists(dest_path): return dest_path stem Path(dest_path).stem suffix Path(dest_path).suffix parent Path(dest_path).parent counter 1 while True: new_name f{stem}_{counter:02d}{suffix} candidate parent / new_name if not os.path.exists(candidate): return candidate counter 1这样保证同一目录下永远不会有覆盖风险。5. 常见问题与排查技巧实录5.1 问题速查表症状可能原因解决方案重命名没触发监听目录不对或截图工具保存到其他目录检查截图工具的保存路径设置确认监听目录识别出的文件名是乱码OCR 对复杂背景识别失败提高置信度阈值加强图片预处理文件名超长模型输出没有压缩清洗函数里强制截断到 80 字符文件名变成“未识别_时间戳”OCR 没提取到文字或 LLM 返回空检查图片是否过于复杂适当降低置信度阈值EXDEV: Cross-device link not permitted源目录和目标目录跨文件系统改用 copy2 remove或规划好目标目录中文文件名出现乱码或空格提示词没约束或模型输出中文字符提示词显式要求小写英文和下划线5.2 EXDEV 错误的本质和排查思路这个错误值得单独说。它的本质是 Linux 的rename系统调用要求源路径和目标路径在同一挂载点内。如果不在内核直接返回EXDEV不给任何商量余地。排查方式很简单df 源文件目录 df 目标文件目录如果两个命令输出的设备名不一样那必然跨设备。跨设备重命名只能通过“复制到目标 删除源文件”实现这个操作不再是原子的过程中如果断电或中断可能出现源文件没了但目标文件没写完的情况。所以我在日志里加了重命名前后的记录中断了也能人工恢复。实测在 Docker 挂载卷和外部磁盘交错使用的环境下这种错误出现频率不低。Screenshotify 的目标目录默认放在被监听目录同一层通常不会跨设备。但如果你手动指定了自定义目标目录比如直接把截图归档到外置硬盘那就要注意跨设备问题。5.3 隐私保护方案这一部分我建议所有做本地 AI 工具的朋友都重视。截图内容往往包含比你意识到的更多的敏感信息。建议三件事第一默认禁止截图内容上传云端。本地模型性能再弱对于生成文件名这个任务也足够了没必要冒数据泄露的风险。第二处理完的截图中如果包含密码、密钥、地址等信息可以加一个可选的“内容脱敏”功能OCR 识别完成后把疑似敏感串替换成***再交给大模型。第三日志里不要记录完整的 OCR 文本最多记录前 50 个字符。我自己最初版本的日志完整记录 OCR 结果排查问题时看起来方便但日志文件一旦泄露就是完整内容泄露所以后来改成截断日志。5.4 性能优化心得Screenshotify 刚做完的时候从截图出现到重命名完成平均要花 4-6 秒主要时间在 OCR 和 LLM 推理上。后面做了三个优化模型常驻内存不要每次调用都重新加载。PaddleOCR 的模型加载很慢反复加载是最亏的。用内存缓存。同一张图如果因为监听事件重复触发直接用缓存结果跳过。批量处理。整理老截图时用单张单张处理太慢改造成批量模式后可以把多个 OCR 任务并发跑LLM 推理也走并发请求。优化后单张截图处理时间压到 1-2 秒体验才算是可用。6. 从Screenshotify延伸出去Screenshotify的后续扩展方向6.1 批量重命名老截图这是被问得最多的需求。我目前版本只监听新的截图但大部分人的痛点其实是历史积累的几千张老照片。扩展思路是加一个bulk子命令喂一个目录遍历所有图片按同样流程重命名。这里要注意的是批量模式下的性能瓶颈不能简单复用单张模式。我建议是先把所有图片的 OCR 结果缓存到本地 sqlite 数据库再分批交给 LLM 生成标题最后统一执行重命名。这样中断了还能从缓存恢复。6.2 按内容自动归档到子目录文件名里有语义信息后一个顺理成章的扩展是按主题分目录比如errors/、receipts/、design_ref/。这本质上是把文件名的分类信息升级为目录结构。实现上可以由模型输出时带上一个category字段重命名时自动创建对应目录并移动文件。6.3 和 AI Agent 工作流结合现在很多人都在把这类小工具嵌进更大的 AI Agent 工作流里。比如监听到一张代码运行结果截图重命名完后自动触发一个 agent 去读取图片内容、生成 bug 分析、创建 issue 草稿。这样截图不只是静态记录而成了自动化工作流的触发源。我目前已经做的一个小实验是监听到“报错类”截图后文件名生成后额外推送通知到手机。后续如果做得更深入可能加一个接口层让外部 agent 都能调用重命名能力。7. 从Screenshotify实际使用中我学到的东西用 Screenshotify 两个多月最明显的一个改观是搜索截图这个动作基本消失了。以前找截图是“打开文件夹慢慢翻”现在是spotlight里敲文件名关键词直接就能定位。这里面的快感很难量化但用过一次就回不去了。我个人的建议是不要一开始就追求全自动先把流程跑通、跑稳再考虑加更多智能功能。我最早版本也尝试过让模型生成非常详细的“描述性标题”结果文件名变得特别长反而难用。后来压缩成 3-8 个单词的短标题效果最好。如果你也想做类似的工具最值得花心思的地方不是模型部署也不是 OCR 调优而是命名规范和异常处理。一个没有规范的名字模型再强也救不了。而异常处理不完善工具就会在真正需要它的时候掉链子。从一次截屏到一段干净的历史记录这中间的每个坑我都替你们踩过了。