我现在都在用 squoosh 批量压缩照片。但是 squoosh 压缩出来的图片丢失了 exif 数据，不论是网页还是终端 squoosh-cli。关于使用终端 squoosh-cli 批量压缩图片，你可以阅读我写的 [可能是]最好的压缩图片程序，使用 squoosh-cli 批量压缩图片 关于在得到压缩后的图片不丢失exif元数据这个问题，我尝试了其他压缩工具，比如 tinypng.com 也一样无法实现目的。 tinypng.com 的 web，压缩后的图片无法得到元数据。然后我关注到了其 API，https://tinypng.com/developers/reference/java 我写了段Java代码试了一下，通过调用方法 source.preserve("copyright", "creation", "location"); 可以保存元数据 System.out.println("start to tiny your images..."); try { String path = "c:\\users\\dk\\downloads\\IMG_1989.jpg"; Source source = Tinify.fromFile(path); String lowercasedPath = path.toLowerCase(Locale.ROOT); Source sourceWithMetadata; if (lowercasedPath.endsWith(".jpg") || lowercasedPath.endsWith(".jpeg")) { //location sourceWithMetadata = source.preserve("copyright", "creation", "location"); } else { sourceWithMetadata = source.preserve("copyright", "creation"); } sourceWithMetadata.toFile("c:\\users\\dk\\downloads\\IMG_1989-dk-compressed.jpg"); } catch (IOException e) { throw new RuntimeException(e); } 但是经过实践发现只保留下来了图片拍摄时间。我想要的相机信息，镜头信息，拍摄参数（比如快门时间、ISO大小、光圈大小）等还是丢失了。 经过一番研究，我最终转变了思路。 使用 exiftool 工具写入照片的原始exif metadata，我之前居然不知道 exiftool 这么好用的工具。 官网 https://exiftool.org/ ExifTool 由 Phil Harvey 开发，是一款免费、跨平台的开源软件，用于读写和处理图像（主要）、音视频和PDF等文件的元数据（metadata）。ExifTool可以作为Perl库（Image::ExifTool）使用，也有功能齐全的命令行版本。ExifTool支持很多类型的元数据，包括Exif、IPTC、XMP、JFIF、GeoTIFF、ICC配置文件、Photoshop IRB、FlashPix、AFCP和ID3，以及众多品牌的数码相机的私有格式的元数据。 本段介绍source 我在 squoosh 容器当中继续操作。 squoosh 容器基于 alpine 镜像制作，所以使用 apk 安装 exiftool 即可。 # apk add exiftool 然后编写一段 shell 脚本。 2024-08-02 19:18:18 新增一些说明 我做的 dko0/squoosh:v1.12.0 镜像是支持amd64, arm64 and arm v7 arch这三个架构的，但是只有在 arm64 机器上运行的容器安装了vim之后才能正常运行。在amd64机器上pull了此镜像后启动一个容器然后通过apk安装vim会无法运行，都会报错Segmentation fault (core dumped)。关于这个问题我检查了很多东西，发现可能是 alpine linux 和不同架构的相关依赖实现的问题 由于 Alpine Linux 使用的是轻量级的 musl libc，而不是更常见的 glibc，这可能会导致某些软件在运行时出现兼容性问题 alpine linux 默认使用 /bin/ash 作为 default shell，而且内置的 vi 等命令都是来自 busybox，简体中文不太好支持。所以下面的 shell 我最终改用 /bin/bash 编写。我放在宿主机 debian 执行了 debian 需要通过 apt-get update && apt-get install -y exiftool 安装 exiftool 工具 #!/bin/bash # 检查是否提供了必要的参数 if [ -z "$1" ] || [ -z "$2" ]; then echo "Usage: $0 <input_dir> <output_dir>" exit 1 fi # 从命令行参数中读取输入目录和输出目录 input_dir="$1" output_dir="$2" # 检查输入目录是否存在 if [ ! -d "$input_dir" ]; then echo "Input directory '$input_dir' does not exist." exit 1 fi # 检查输出目录是否存在 if [ ! -d "$output_dir" ]; then echo "Output directory '$output_dir' does not exist." exit 1 fi # 如果 output_dir 以 / 结尾，删除末尾的 / if [ "${output_dir: -1}" = "/" ]; then output_dir="${output_dir%/}" fi # 定义要处理的文件扩展名 exts=("jpg" "JPG" "jpeg" "JPEG") # 遍历所有指定文件扩展名的图片文件 for ext in "${exts[@]}"; do for input_file in "$input_dir"/*."$ext"; do # 检查文件是否存在 if [ ! -f "$input_file" ]; then continue fi # 提取文件名，不带路径 filename=$(basename "$input_file") # 构建目标文件路径 output_file="${output_dir}/${filename%.*}-dk-compressed.jpg" # 检查压缩后的文件是否存在，以防万一 if [ ! -f "$output_file" ]; then echo "File $output_file does not exist, skipping..." continue fi # 复制 EXIF 元数据 exiftool -TagsFromFile "$input_file" -all:all "$output_file" echo "$output_file exif metadata write success." # 删除生成的备份文件（-TagsFromFile 操作会创建一个备份文件） rm "${output_file}_original" done done 将此 shell 文件保存为 /home/dk/copy_exif.sh 使用时的命令：~/copy_exif.sh . ./output/ 解释说明： 该脚本从命令行参数中读取输入目录和输出目录，可以是相对路径。会将输入路径下的所有 xxx.jpg 的元数据读取出来，写入当前文件夹下的 output 目录中的 xxx-dk-compressed.jpg 文件中，这样刚好能确保 exif metadata 有正确的归属关系 记住添加可执行权限 chmod +x /home/dk/copy_exif.sh 我使用 dk 这个用户执行，因为 squoosh-cli 也是在 dk 这个用户下执行的。不在 root 下操作是不希望破坏文件权限和所有者，宿主机内的文件系统还通过 samba 挂载了，其他设备访问时如果是 root 用户操作的会无法删除文件（没有写权限） 经过 exiftool 写入exif元数据之后，文件会变大一丢丢。通过下面一个case 粗略估计文件体积会增大5%。 可以看到压缩后的照片也具有了元数据了，这对我将文件存放在 immich 里时可以有更好的读写性能，并且在浏览照片时也能看到照片 exif 信息，这对我很重要。 当然具备完整细节的原图还在SD卡/电脑里备份着。
思路就是编写启动和停止的 shell 脚本，然后编写 systemd 配置文件。 我以我之前写的微博热搜实时推送前后端程序为?️。欢迎关注我的 微博热搜实时推送 tg 推送频道。 url is https://t.me/weibo_hot_search GitHub: https://github.com/hellodk34/weibo_hot_search 该 Java 应用是这样运行的 java -jar xxx.jar > /dev/null 2>&1 & 该 react应用是这样运行的 serve -s build > /dev/null 2>&1 & Java 应用 编写 start-wb-hotsearch.sh 文件 注意 jar 文件的绝对路径 #!/bin/bash /usr/local/java/jdk1.8/bin/java -jar /xxx/wb_hotsearch/wb-hotsearch-2021-12-22.jar > /dev/null 2>&1 & echo $! > /var/run/wbhotsearch.pid 编写 stop-wb-hotsearch.sh 文件 #/bin/bash PID=$(cat /var/run/wbhotsearch.pid) kill -9 $PID rm -rf /var/run/wbhotsearch.pid 编写 systemd service 文件 在 /usr/lib/systemd/system/ 下创建 wbhotsearch.service 文件，内容如下。注意两个shell文件的绝对路径要写对，下面给出的是假的路径 [Unit] Description=Weibo hot search back end After=network.target [Service] Type=forking ExecStart=/xxx/wb_hotsearch/start-wb-hotsearch.sh ExecStop=/xxx/wb_hotsearch/stop-wb-hotsearch.sh [Install] WantedBy=multi-user.target 配置 systemd 先执行 systemctl daemon-reload 再 enable 然后 start 即可。下面的 status 和 is-enabled 可以帮助查看服务状态 systemctl enable wbhotsearch systemctl start wbhotsearch systemctl status wbhotsearch systemctl is-enabled wbhotsearch React 应用 殊途同归，React 应用也是如此，下面简要写一下 编写 start-wbhotsearch-front.sh 文件 #!/bin/bash /usr/bin/serve -s /xxx/wb_hotsearch/wb-hotsearch-front-end/build > /dev/null 2>&1 & echo $! > /var/run/wbhotsearchfront.pid 编写 stop-wbhotsearch-front.sh 文件 #/bin/bash PID=$(cat /var/run/wbhotsearchfront.pid) kill -9 $PID rm -rf /var/run/wbhotsearchfront.pid 编写 systemd service 文件 在 /usr/lib/systemd/system/ 下创建 wbhotsearch-front.service 文件，内容如下。注意两个shell文件的绝对路径要写对，下面给出的是假的路径 [Unit] Description=Weibo hot search front end service After=network.target [Service] Type=forking ExecStart=/xxx/wb_hotsearch/wb-hotsearch-front-end/start-wbhotsearch-front.sh ExecStop=/xxx/wb_hotsearch/wb-hotsearch-front-end/stop-wbhotsearch-front.sh [Install] WantedBy=multi-user.target 配置 systemd 先执行 systemctl daemon-reload 再 enable 然后 start 即可。下面的 status 和 is-enabled 可以帮助查看服务状态 systemctl enable wbhotsearch-front systemctl start wbhotsearch-front systemctl status wbhotsearch-front systemctl is-enabled wbhotsearch-front ok了，这下重启机器，我的这些服务将能够自动启动。 之所以没有用 docker，是因为太占空间了，我的 vps 存储空间告急，昨天已经另外申请了 10GB ssd 了，付款了12美元。 done~~~
分享一个 git add、git commit、git push 的快捷工作流工具
gitpushworkflow feature description: git add . and git commit -m and git push workflow 一个不是那么复杂的 git add .、git commit -m 'xxx'、git push 的工作流脚本工具。 工具：Click Me Usage 1. in macOS or any Linux systems cp linux-gitpush.sh to any folder you like, such as /path/of/linux-gitpush.sh at current git repo's work dir, just run bash /path/of/linux-gitpush.sh 2. in Windows cp windows-gitpush.bat to any folder you like, such as D:\, after copying, this file's absolute path is should be D:\windows-gitpush.bat at current git repo's work dir, and just run d:\windows-gitpush.bat Example in macOS or Linux this output is like this huadekaideMBP:notes-2021 huadekai$ bash linux-gitpush.sh this a git push workflow in macOS or Linux, using a shell file current time is: 2021-07-04 15:22:30 current work dir is: /Users/huadekai/Downloads/personal-projects/github/notes-2021 first step: git add . second step: git commit -m 'xxxx' [master b15a526] 2021-07-04 15:22:30 update 1 file changed, 1 insertion(+), 16 deletions(-) Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 12 threads Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 293 bytes | 293.00 KiB/s, done. Total 3 (delta 2), reused 0 (delta 0) remote: Resolving deltas: 100% (2/2), completed with 2 local objects. To github.com:hellodk34/notes-2021.git 3c5905e..b15a526 master -> master third step: git push. ending. the end of the workflow. in Windows the output is like this D:\repo\github\notes-2021>windows-gitpush.bat this a git push workflow in Windows, using a bat file current time is: 07/04/2021 Sun 15:19:31.45 current work dir is: D:\repo\github\notes-2021 first step: git add . Waiting for 0 seconds, press CTRL+C to quit ... second step: git commit -m 'xxx' [master 9e9da30] 07/04/2021 Sun 15:19:33.16 update 3 files changed, 42 insertions(+), 13 deletions(-) Waiting for 0 seconds, press CTRL+C to quit ... third step: git push Enumerating objects: 9, done. Counting objects: 100% (9/9), done. Delta compression using up to 16 threads Compressing objects: 100% (5/5), done. Writing objects: 100% (5/5), 918 bytes | 918.00 KiB/s, done. Total 5 (delta 4), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (4/4), completed with 4 local objects. To github.com:hellodk34/notes-2021.git eddc27a..9e9da30 master -> master the end of the workflow. 注意事项 默认 git add 的是当前本地工作区的所有已改动文件 git commit -m，提交的 message 信息，默认是时间+update 字符串拼接出来的 使用 vscode 时，快捷键 control + shift + ` 默认新建一个终端，此时所在工作目录就是 git 项目的根目录，直接执行 bash /path/of/linux-gitpush.sh 或 d:\windows-gitpush.bat 即可 Windows 下 git.exe 建议安装 Git Bash end.
写shell脚本监测vps上的frps进程是否存在，不存在自动重启
写下面文本到文件 /home/sftp/check_frps.sh #!/bin/bash # author: hellodk # time: 2021-05-28 16:14:26 # script feature description: check if frps is running, if not, start it function check_frps(){ time3=$(date "+%Y-%m-%d %H:%M:%S") frpsCount=`ps aux | grep "/usr/local/frp/frps -c /usr/local/frp/frps.ini" |grep -v grep |wc -l` if [ 0 == $frpsCount ]; then echo $time3 "current running frps process count: "$frpsCount ", meaning frps daemon is dead, now start it" >> /home/sftp/check_frps.log nohup /usr/bin/systemctl start frp >> /home/sftp/check_frps.log 2>&1 & else echo $time3 "current running frps process count: "$frpsCount ", and no need to start frps" >> /home/sftp/check_frps.log fi } check_frps 脚本是没有问题的，使用bash执行也是正常，日志打印也正常，但就是在我的那台硬件配置比较差的vps上总是无法使用 crontab 保存执行 每次使用 crontab -e 新增一行然后保存退出，成功提示crontab: installing new crontab 但是过了一段时间后发现这行就不见了…… 脚本自然也就没有按照预期执行。 由于这一台主机挂了京东薅羊毛的脚本，后续发现了问题所在。 在狗东薅羊毛控制面板（web端）直接编辑了这个cron list，成功新增一行，web的提示是成功修改了 crontab.list 文件，后面通过 find 命令找到了 # find / -name crontab.list /opt/jd/config/crontab.list 我应该直接修改这个文件，新增我的cron计划。 需要额外重点关注：crontab -e 命令编辑的文件是 /var/spool/cron 目录下的具体的用户名文件 比如 root, 具体文件是 /var/spool/cron/root 0 0-23/1 * * * bash /home/sftp/check_frps.sh, 含义是每隔一小时执行一次这个 shell 脚本 或者 0 */1 * * * bash /home/sftp/check_frps.sh 这两个命令都代表每隔一小时执行一次（也就是每小时检查一次frps进程是否存在） 将这行新增到 /opt/jd/config/crontab.list 文件末尾保存退出即可。后续查看脚本发现稳定运行~ 看看日志 # tail -n 15 /home/sftp/check_frps.log 2021-06-06 20:00:01 current running frps process count: 1 , and no need to start frps 2021-06-06 21:00:02 current running frps process count: 1 , and no need to start frps 2021-06-06 22:00:01 current running frps process count: 1 , and no need to start frps 2021-06-06 23:00:01 current running frps process count: 1 , and no need to start frps 2021-06-07 00:00:04 current running frps process count: 1 , and no need to start frps 2021-06-07 01:00:02 current running frps process count: 0 , meaning frps daemon is dead, now start it 2021-06-07 02:00:02 current running frps process count: 1 , and no need to start frps 2021-06-07 03:00:02 current running frps process count: 1 , and no need to start frps 2021-06-07 04:00:02 current running frps process count: 1 , and no need to start frps 2021-06-07 05:00:01 current running frps process count: 1 , and no need to start frps 2021-06-07 06:00:01 current running frps process count: 1 , and no need to start frps 2021-06-07 07:00:02 current running frps process count: 1 , and no need to start frps 2021-06-07 08:00:02 current running frps process count: 1 , and no need to start frps 2021-06-07 09:00:01 current running frps process count: 1 , and no need to start frps 2021-06-07 10:00:02 current running frps process count: 1 , and no need to start frps 问题解决了。end.