有段时间没折腾NAS上的图片处理了,直到前阵子整理绿联NAS里的照片,两千多张手机原图堆在一起,既占地方又没法统一转成WebP格式给网站用,才意识到一台常驻运行、能批量压缩、能转格式的小工具有多刚需。后来我把 mazanoke 部署到了绿联NAS上,跑了两个多月,从命令启动到批量处理照片再到接口调用,整体体验比较顺。这篇东西就把整个部署和使用过程记录下来,包括中间踩过的权限、编码、内存相关的坑,给同样在绿联或者其他NAS上想搞图片压缩和格式转换的朋友一个可以直接抄作业的参考。
1. 为什么我最终选mazanoke:图片压缩与格式转换的痛点
先说清楚我自己的场景。日常拍的照片集中在手机里,一年下来小一万张,JPG和HEIC混着来;同时我还有个博客和一个素材站,需要把不少图统一压成WebP或者AVIF再传上去。以前的做法是周末抽个时间,用电脑上的批处理软件跑一轮,或者直接在线压缩。但这两条路都有挺别扭的地方。
1.1 传统批量处理的三个麻烦
第一是效率低。电脑上的批处理工具大部分是“打开软件—导入—设置参数—导出”,操作一遍没问题,但如果每个月都要重复做,就很烦。中间如果突然来一张超大尺寸的全景图,内存占用直接上去,软件卡死是常有的事。第二是文件分散。照片散在手机、相机SD卡、NAS多个目录里,每次都要先拷贝到电脑一个固定目录再处理,处理完再拷回去,来回折腾。第三是格式转换太琐碎。HEIC转JPG、JPG转WebP、PNG转AVIF,每个场景参数不一样,一套工具往往只擅长某一个方向,要同时装好几个软件。
在线压缩更不放心。手机照片包含拍摄时间、GPS位置这些元数据,传上去再下载,等于把隐私交给别人。我自己的素材站图片还涉及版权问题,更不敢往第三方平台传。
1.2 mazanoke的定位:一个常驻NAS的小型图片处理服务
后来在找方案的时候看到 mazanoke 这个项目,它的定位很直接:一个跑在容器里的图片压缩和格式转换服务。部署到NAS之后,它就一直在线,我只需要打开浏览器进Web界面,或者用脚本调它的接口,就能完成批量处理。
它和普通单机软件最大的区别在于“服务化”。软件是每次要用的时候打开,服务则是一直待命,目录里扔进去一批图,就能处理一批。配合NAS本身7x24小时开机的特性,等于有了一个全屋可用的图片处理后台。手机上的图传到NAS的输入目录,在电脑上打开Web界面点几下,处理完的结果自动落到输出目录,整个过程不用装任何客户端。
1.3 与常用替代方案的硬指标对比
这里拿我自己实际用过的几种方案和mazanoke做一个横向对比,涉及Scriptable的Pillow脚本方案、XnConvert、群晖的Synology Photos自带转换功能。
| 方案 | 部署方式 | 批量处理 | 格式覆盖 | API/自动化 | 隐私 | 适合场景 |
|---|---|---|---|---|---|---|
| Python + Pillow脚本 | 命令行脚本 | 支持 | 看装了哪些解码器 | 支持 | 本地 | 熟悉代码、一次性处理 |
| XnConvert | Windows软件 | 支持 | 很全 | 弱 | 本地 | 偶尔用电脑处理 |
| Synology Photos | 群晖套件 | 只支持缩略图/原图导出 | PHOTO转码有限 | 不支持 | 本地 | 群晖用户、简单场景 |
| mazanoke | Docker容器 | 支持 | 常见格式齐全 | 支持 | 本地 | NAS常驻、需要自动化 |
从表里能看出来,如果只是“女朋友手机里存了几百张图让我帮她压一下”,用XnConvert就够了。但如果目标是“每个月自动处理一批图,格式和质量参数固定,还要能嵌入到自己的工作流里”,那服务化的mazanoke明显胜出。这也是我选它的核心理由:一遍配置,长期使用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前的准备:绿联NAS上的环境与目录规划
绿联的NAS现在主打UGOS Pro系统,Docker是系统内置的功能,不需要额外装。我用的机型是DXP4800,处理器是x86架构,跑常见的x86镜像没什么问题。如果你用的是ARM架构的机型,部署前最好确认一下镜像是否有对应架构的版本,免得拉下来跑不起来。
2.1 确认Docker环境
打开绿联系统的“Docker”应用,能看到容器、镜像、网络这几个管理入口。新一点的UGOS Pro版本内置了Docker Compose的支持,这比在群晖上还要手动开启Container Manager更省事。如果界面里没有Compose入口,也可以通过SSH连上去用命令行操作。
SSH登录默认是关闭的,需要在绿联控制台里手动开启。开启后直接用终端连到NAS的IP,用户默认是admin,密码就是操作系统的密码。连上之后先跑一个简单命令确认Docker状态:
bash复制docker version
docker compose version
两个命令都能正常返回版本信息,就说明Docker环境是OK的。这里要提一个经验:绿联NAS的系统盘空间比较珍贵,Docker默认的数据根目录在系统盘上。如果你计划长期跑图片处理,一定要把容器的工作目录和数据目录放到存储池的大分区上,而不是放在系统盘里。
2.2 目录结构设计
部署前我先把目录结构规划清楚了,这个步骤看起来繁琐,但实际能省掉很多后续麻烦。我的目录结构是这样的:
code复制/volume1/docker/mazanoke/
├── input/
├── output/
├── config/
└── docker-compose.yml
input和output分别代表输入目录和输出目录,这样设计的目的有两个:一是容器内外的路径映射一目了然,不需要登录到容器里找文件;二是方便配合手机App或者同步工具,直接把照片转存到input目录。config目录放配置文件,如果需要修改转换参数、调整画质,不用重建容器。
合理拆分目录还有一个隐藏好处:备份和清理非常方便。我可以在NAS的定时任务里设置只清理output目录,绝不碰input和config,避免误删原始文件。
2.3 镜像拉取与网络问题
部署前还有一个容易卡住的地方:镜像拉取。绿联自带的Docker管理界面里有一个镜像搜索功能,但如果你当前网络访问Docker Hub比较慢,搜索和拉取都可能会超时。这个情况我自己遇到过,等了几分钟都拉不完,而且没有进度提示,看起来像卡死了一样。
解决办法有两个,都是常规操作。
第一,在绿联Docker设置里手动配置镜像加速地址。绿联的Docker管理界面通常提供了镜像仓库设置项,在里面填入你常用的镜像加速器地址,保存后再拉取,速度能提升不少。第二,如果镜像加速地址也不稳定,可以在一台能正常访问Docker Hub的电脑上执行 docker pull 把镜像打包成tar文件:
bash复制docker pull mazanoke/image-tool:latest
docker save mazanoke/image-tool:latest -o mazanoke-image.tar
然后把tar文件拷贝到NAS上,使用 docker load -i mazanoke-image.tar 导入。这条路径在网络不稳定的环境里非常可靠,值得记一下。
提示:不同版本的mazanoke镜像名和端口可能不完全一样,以项目仓库里标注的镜像名为准。下面所有示例均基于我在用的镜像和默认端口,换成其他版本时只需改对应参数即可。
3. 核心部署流程:从Compose文件到Web界面访问
整个部署过程的核心就是写一份docker-compose.yml,然后启动容器。这里把每一步的来龙去脉说清楚,方便你根据自己的NAS型号和目录结构调整。
3.1 编写docker-compose.yml
我先在/volume1/docker/mazanoke目录下创建docker-compose.yml文件:
yaml复制services:
mazanoke:
image: mazanoke/image-tool:latest
container_name: mazanoke
restart: unless-stopped
ports:
- "3880:3880"
environment:
- TZ=Asia/Shanghai
- OUTPUT_QUALITY=80
- ENABLE_WEBUI=true
volumes:
- ./input:/data/input
- ./output:/data/output
- ./config:/data/config
# 以下两项根据NAS实际内存调整,避免容器占用过大
mem_limit: 2g
pids_limit: 512
逐行解释下关键参数:
image指定了要运行的镜像,container_name给容器起了个固定名字,方便后面用docker命令操作。restart: unless-stopped表示容器异常退出或NAS重启后自动拉起,这是NAS上跑服务的标配设置,不然每次重启NAS都要手动启动容器就太蠢了。
ports把容器内3880端口映射到宿主机同样端口,这样局域网里的其他设备都能访问。environment里设置了时区和默认输出质量。这里的OUTPUT_QUALITY=80是我自己根据素材用途设的默认值,压成WebP放在网页上,视觉无损,体积能比原图小一半以上。
volumes把宿主机的三个目录挂载到容器内部。这里有个细节要注意:./input这种相对路径是相对于docker-compose.yml所在目录的。所以我把compose文件和input、output、config放在同一级目录下,这样路径最直观。
mem_limit: 2g限制了容器最大内存。mazanoke底层用的是libvips这种高性能图像处理库,正常处理一张2000万像素照片只要几十MB内存,但极端情况下如果一次塞进大量超大图,内存还是会涨。设个限制能防止它把NAS整体内存吃满。pids_limit则是限制进程数量,防止异常时fork炸弹,属于保险项。
3.2 命令行直接部署方式
如果你不想用Compose,也可以用docker run一条命令完成部署。很多人不熟悉Compose,直接命令行更直观:
bash复制docker run -d \
--name mazanoke \
--restart unless-stopped \
-p 3880:3880 \
-e TZ=Asia/Shanghai \
-e OUTPUT_QUALITY=80 \
-e ENABLE_WEBUI=true \
-v /volume1/docker/mazanoke/input:/data/input \
-v /volume1/docker/mazanoke/output:/data/output \
-v /volume1/docker/mazanoke/config:/data/config \
--memory 2g \
--pids-limit 512 \
mazanoke/image-tool:latest
和Compose文件是一一对应的。如果你是命令行新手,建议还是用Compose,因为Compose文件可以修改后重新执行 docker compose up -d 来应用变更,而docker run创建的容器如果需要改参数,要删掉重建,不太方便。
3.3 启动与验证
启动命令:
bash复制cd /volume1/docker/mazanoke
docker compose up -d
第一次启动会拉取镜像,时间取决于网络和镜像大小。启动完成后,先看容器状态:
bash复制docker ps | grep mazanoke
状态显示Up就说明容器正常。再查看日志确认没有报错:
bash复制docker logs mazanoke
日志正常的情况下,打开浏览器访问 http://你的NAS的IP:3880,看到Web界面就说明部署成功了。我第一次部署时在这里遇到了一个小坑:绿联NAS默认开启了防火墙,所有来自局域网的连接都需要放行端口,3880端口如果没有在防火墙规则里开放,浏览器会一直转圈。解决办法很简单,在绿联控制台的防火墙设置里加一条放行3880端口的规则,或者如果只是局域网使用,直接关掉防火墙验证一下也行。
4. 实际使用:从Web界面到API调用的完整过程
部署完成后就是日常使用。mazanoke给我最大的惊喜是它的使用方式足够灵活,不熟悉的用户可以直接用Web界面,喜欢自动化的可以通过API集成到脚本里。这两条路我都走了一遍,分别说下操作细节。
4.1 Web界面批量处理图片
打开Web界面后,第一眼就是一个上传区域和一个参数面板。操作逻辑基本是:选择输入图片、选择输出格式、设置压缩质量、点击转换、下载结果。
我日常最常用的操作是把一批JPG压成WebP。流程是:在/volume1/docker/mazanoke/input目录里先放好要处理的图片,然后在Web界面上点击“从输入目录载入文件”,界面会自动列出input目录里的所有图片,我勾选需要的文件,把目标格式设为WebP,质量调到80,再点转换。处理完成后,输出目录里就会出现对应的WebP文件,文件名保持不变,只是后缀名变了。
这样设计的好处是,即使不使用界面里的上传功能,只要往input目录扔文件,界面里就能看到,处理和下载完全不用额外的文件传输。对于多张图片的批量场景,这个流程特别顺手。
4.2 REST API调用方式
Web界面适合交互操作,如果要自动化,就得用API。mazanoke对外暴露了一套简单的REST接口,我用curl调用过多次,稳定可靠。拿一个实际例子来说,我写了一个脚本,每周末自动处理某个目录下的所有JPG,把它们转成WebP并压缩到合适尺寸。
单张图片转换的API调用是这样的:
bash复制curl -X POST http://192.168.1.100:3880/convert \
-H "Content-Type: application/json" \
-d '{
"input": "/data/input/photo_001.jpg",
"output": "/data/output/photo_001.webp",
"format": "webp",
"quality": 80
}'
这里input和output路径不是NAS上的宿主路径,而是容器内部的路径。因为我们之前把宿主机./input目录映射到了容器内的/data/input,所以API里对容器内部路径的引用要和挂载关系对应起来。如果搞混了宿主路径和容器路径,API会返回404或者文件找不到的错误,这个一定要留意。
批量处理的话,在设计上推荐循环调用,每次调用传一个文件。好处是处理进度直观,即使某个文件格式不兼容导致报错,也不会影响其他文件,可以保证批量任务整体不中断。
4.3 用shell脚本做定时批处理
API能调用之后,自动化就顺理成章了。我在绿联NAS上用crontab写了一个每周日的定时任务,脚本内容大致如下:
bash复制#!/bin/bash
INPUT_DIR="/volume1/docker/mazanoke/input"
OUTPUT_DIR="/volume1/docker/mazanoke/output"
API_ENDPOINT="http://127.0.0.1:3880/convert"
for img in "$INPUT_DIR"/*.jpg; do
[ -e "$img" ] || continue
filename=$(basename "$img" .jpg)
curl -s -X POST "$API_ENDPOINT" \
-H "Content-Type: application/json" \
-d "{\"input\": \"/data/input/${filename}.jpg\", \"output\": \"/data/output/${filename}.webp\", \"format\": \"webp\", \"quality\": 80}" \
|| echo "error: $filename"
done
这里API_ENDPOINT用了127.0.0.1,因为脚本跑在NAS本机上,走回环地址更快更安全。filename=$(basename "$img" .jpg)是取出不带路径和扩展名的文件名,用于构造输入和输出路径。
把脚本放到/volume1/docker/mazanoke/目录下,加执行权限,然后把crontab设置进去:
bash复制crontab -e
在打开的文件里加一行:
cron复制0 3 * * 0 /volume1/docker/mazanoke/process_images.sh
意思是每周日凌晨三点执行一次。设置完成后,只要每周日之前把要处理的图片丢进input目录,第二天就会自动得到压缩好的WebP文件。配合绿联的相册备份功能,手机照片自动传到NAS后,再由这个脚本统一压缩,日常基本不用管了。
4.4 元数据保留问题
压缩和格式转换过程还有一个不太起眼但很重要的细节:图片元数据的保留。Exif信息里包含拍摄时间、相机型号、镜头参数,地理坐标等。不同的转换工具对Exif的处理策略不一样,有些默认丢弃,有些则保留。
mazanoke在默认情况下会保留大部分Exif信息,只会在转成WebP时有个别字段缺失。这对我这种需要保留拍摄时间的用户非常友好,整理照片墙的时候能看到每一个文件的拍摄时间,而不是统一变成转换时间。如果你的实际需求是彻底抹掉位置信息再分享,建议在Web界面参数面板里找到相关开关,手动关闭元数据保留,这一步对隐私保护很重要。
5. 踩坑实录:部署和使用中遇到的五个真实问题
任何工具用起来都不可能一帆风顺,mazanoke在绿联NAS上跑下来也遇到了不少问题。下面这几个是最典型的,每一个都值得单独记录,因为你大概率也会碰到。
5.1 容器内的读写权限问题
第一次挂载目录后启动容器,我往input目录里扔了几张测试图,然后打开Web界面准备转换,结果发现图片列表是空的。去查日志,发现容器根本没有读取input目录的权限。
排查过程是这样的:先确认宿主机上input目录的权限是不是755,目录属主是admin。接着进到容器里看一下挂载是否成功:
bash复制docker exec -it mazanoke ls -l /data
发现/data/input存在,但权限显示为drwxr-xr-x,属主是root。宿主机上的admin用户对input目录的写入权限是有的,但容器内的进程是以root用户跑的,从容器内访问宿主目录时,Linux的权限机制会检查目录的属主和权限,如果目录属主不是root,而other权限只有读和执行,容器进程就无法写入。
解决方式是在Compose文件里加上user字段,把容器内进程的用户指定为宿主机的admin UID。绿联NAS上admin用户的UID通常是1000,在Compose文件里加一行:
yaml复制 user: "1000:1000"
重启容器后,读写权限问题就解决了。这里建议用id -u admin先确认一下admin用户的UID,不同型号的NAS可能不一样。
5.2 中文文件名乱码
第二个坑是中文文件名。我的很多照片从手机备份出来,文件名形如“2023年旅行_张家界.jpg”,这种带中文和下划线的名字在Web界面和API里都出现了乱码。
排查后确定是编码不一致导致的。宿主机上的文件名是UTF-8编码,但容器内某个环境变量默认用了POSIX编码,导致文件名解析错乱。解决方式是在Compose环境变量里显式设置编码相关变量:
yaml复制 environment:
- LANG=C.UTF-8
- LC_ALL=C.UTF-8
重启容器后,中文文件名在Web界面里能正常显示了,API按文件名传递路径也不会再报文件不存在。
5.3 大图导致容器内存溢出
在处理一批由全景图拼接生成的高分辨率图片时,某个转换任务直接把容器干崩溃了。查日志发现是OOM Killed。原因很简单,那张全景图分辨率为18000x6000像素,原始JPG虽然不大,但解码成原始RGBA位图后占用的内存相当可观,每个像素按4字节算,就是1800060004字节,约432MB,再加上转换过程中的临时缓冲,内存占用轻松超过1GB。
我的Compose文件里设了mem_limit: 2g,按道理应该够,但问题在于mazanoke处理大图时存在一个并发策略,如果同时有多个转换任务,总内存会叠加。解决办法分两步:一是把并发任务数降下来,在环境变量里设置最大并发为1;二是给容器再加点内存限制空间,改成mem_limit: 3g。改完后再跑那批全景图,稳定没有再次崩溃。
5.4 端口冲突
另一个例子是端口冲突。我NAS上已经跑了一个图片相关服务占用3880端口,mazanoke启动时直接提示端口被占用。查了半天才发现冲突源是一个之前部署的旧容器还挂在后台。为了避免这种问题,建议在部署之前用命令查看端口占用情况:
bash复制lsof -i :3880
或者直接换成其他不常用的端口,比如3881。映射关系改成3881:3880即可,访问地址相应变成 http://NAS的IP:3881。这个小问题只要提前查一下,完全能避免。
5.5 转换后元数据时间异常
最后一个问题比较隐蔽,是转换后的文件时间戳变成当前时间了。这样整理照片时会看到WebP文件的时间全部是同一天,失去了原始的拍摄时间线索。排查后发现这个现象和图像处理库的默认行为有关,转换生成的图片文件在文件系统层面的mtime会更新为处理时间,而图片内部的Exif拍摄时间则不受影响。
我自己的处理办法是,在Web界面操作时,直接按Exif拍摄时间排序管理文件,而不是依赖文件系统时间。同时,在批量脚本中,如果确实需要保留文件时间,可以通过 touch 命令在转换完成后手动设置文件时间为原始文件时间戳:
bash复制touch -r "$img" "$output_webp"
这样文件时间就和原图保持一致了,时间线不再错乱。
6. 进阶玩法:把mazanoke接入NAS日常工作流
跑通基本功能之后,可以琢磨一下更高效的用法。这里的思路是让mazanoke不仅仅是一个“手动转换工具”,而是成为NAS上图片处理的一个中台,其他应用都可以通过API去调用它。
6.1 配合相册同步实现全自动压缩
绿联NAS自带相册备份功能,手机App可以把照片自动备份到指定目录。如果我们把备份目标目录配置成mazanoke的input目录,再通过定时任务定期压缩,就能做到手机照片自动入库后自动变成WebP缩略版,既节省空间又保留原始图片。
这个流程完整跑通后,我基本上不再需要手动打开电脑去处理照片了。手机上的照片当天传到NAS,第二天凌晨脚本自动完成压缩和格式转换,网页上用的所有配图素材都是最新且压缩过的。
6.2 在站务脚本中调用API
我素材站的发布系统是用宝塔面板搭的,内容更新时经常需要把一些临时图片转成适应网页排版大小的格式。以前都是先手动压缩再上传,现在直接在发布脚本里加一段curl调用,让mazanoke处理,再通过SFTP把结果拉回到服务器。整个发布流程在10秒内就能完成,不用再启动电脑上的本地工具。
API调用接口在前面已经给过示例了,这里补充一个要点:调用前最好先判断源文件是否存在,避免API返回错误导致脚本中断。可以在脚本里加一个[ -f "/volume1/docker/mazanoke/input/$filename" ]的判断,确保文件没问题再发请求。
6.3 性能调优建议
根据NAS的硬件配置做性能调优也很重要。绿联NAS不同型号的处理器和内存差异比较大,比如四盘位型号通常配的是N100或更高级别的x86芯片,双盘位入门款则可能是ARM架构。我实测下来,N100处理器处理一张2000万像素照片转WebP大约需要1到2秒,处理速度完全够用。
对于并发和内存,建议按NAS实际内存来设置mem_limit。8GB内存的NAS,可以给mazanoke分配2GB到3GB;4GB内存的入门款,建议限制到1GB,同时将并发任务数设为1,避免因为内存不足导致系统整体卡顿。功能上虽然压缩速度会变慢一些,但NAS本身还有其他服务在跑,稳定更重要。
6.4 群晖、飞牛等NAS的部署差异
最后说一下mazanoke在其他NAS上用的情况。群晖NAS的Docker是Container Manager套件,路径结构和绿联不同,群晖的docker目录一般在/volume1/docker下,差别不大。部署流程基本一致,只是挂载路径要跟着改。权限那块,群晖的普通用户默认不能直接写/volume1/docker下的目录,需要先在File Station里给对应目录设置读写权限,这点和绿联不太一样。
飞牛NAS也有Docker管理界面,而且它基于Debian系系统,SSH和目录操作更接近标准Linux,部署起来反而不容易遇到权限沟壑。在飞牛上部署时,主要注意路径要写绝对路径,不要依赖相对路径即可。
总的来看,mazanoke部署的重点不是NAS品牌,而是把“目录映射、用户权限、端口、内存限制”这四件事搞明白,换到任何NAS上都能顺利用起来。
最后分享一个我实际使用中的小技巧:参数调整时不要直接在Web界面上测试一遍就完事,可以写个简单的shell循环,用同一张原图分别以60、70、80、90的质量参数转换,再对比输出文件大小和肉眼观感。我最终选定的80就是通过这种方式确定的。压缩质量和文件体积最佳平衡点会因为图片来源不同而变化,自己在NAS上花十几分钟批量跑一轮测试,后面一整年都能直接受益。
