闲来无事,我总喜欢泡在图书馆,倒不是喜欢里面的气氛(一群学生绷着神经备考的酸味令人作呕),而是喜欢书架。书总是看不完的,而实体书架有重要的浏览式查询功能(于良芝语),你可以在图书室里随意走动(所以我不喜欢那些考生,好像裤脚的摩擦声都会导致他们落榜一样),摸索书脊,偶若觉得哪一本有趣,就随手拾起或抽出。
易言之,实体书架提供了逛的功能,大概电子设备很难模仿(Vision Pro 倒是有希望,但我更愿意办一张图书馆的会员卡)。即便勉强效颦,如果还得专程打开一个软件,恐怕也是徒增障碍:工作如此忙碌,谁还有闲情逸致专门装一个模拟书架的软件呢?
偶然间,我在听歌时有了灵感。iTunes(现在的 Apple Music)有个原生的屏幕保护插件,可以在屏保上随机显示专辑,点击后还可以播放,简直就像数字版的唱片店。锁屏时,你暂时从工作中抽身,电脑也进入了佐藤大所谓的“休息时间”,变成了另一种设备,有人把它简单当作大号时钟,有人把它变成电子相框,也有人把它充作赛博水族馆。如果拓展一下思路,在屏保里随机展示藏书封面,岂不就能最大程度接近图书馆书架?
可惜屏保插件也要开发者认证,我不愿意既要自己造轮子还得花一大笔冤枉钱(足够我在好几个一流图书馆办卡了),只好先用原生相册屏保凑合。效果倒也不差,macOS 可以随机显示目标文件夹中的图片,只要我预先备妥书籍封面文件,就可以在休息时看到随机呈现的书籍。屏保内容每次都不一样,我时不时会发现几本过去想看但最终不了了之的大部头,有时还会突然撞上一本踏破铁鞋未能忆起的工具书,甚至看到一些我自己都忘了是什么的古书或小语种著作(我常去古玩市场买旧书,有时也海淘原版书,忘了自己买了什么也在所难免)。
为了尽可能填满屏幕,macOS 似乎会拉伸和裁剪一部分封面图像,确保书籍封面尺寸统一。一般也无伤大雅,毕竟书店畅销书书架上的作品尚且参差不齐(我说的是尺寸,真的没有别的意思,真的……),macOS 的处理反倒比书店书架看上去更顺延,偶尔遇到贴边的标题被裁掉,也不至于完全猜不出书名是什么。至于本就是横版的书籍,则会占用两个位置,我有一些建筑类图书和画册,时不时在屏保上瞥见时,效果也不错。
制作这样的书架式屏保也不难,无非两步,先备好书籍封面照片,然后在系统设置中设好屏保。第一步比较关键,你得想办法弄到藏书封面的图片,我用的管理工具是 Calibre,它独立存储了书籍封面文件,我得以批量提取这些封面图片。下面是一个毫无美感但可用的 Shell 脚本,保存为 .sh 格式后在终端(Terminal)中运行,就能导出 Calibre 书库中封面文件。
#!/bin/bash
SRC_DIR="请填写 Calibre 文件夹路径"
DEST_DIR="$HOME/Downloads"
find "$SRC_DIR" -type f -name "cover.jpg" | while read -r FILE; do
DEST_FILE=$(printf "%s/cover%04d.jpg" "$DEST_DIR" "$COUNTER")
cp "$FILE" "$DEST_FILE"
done
显然,这只是权宜之计,因为我隔三差五就要收录新书,经常手动跑脚本并不方便(暂未设计增量机制,每次都要从头跑一遍),而且不分青红皂白导出所有书籍的封面,偶尔也会展示一些我并不想示人的书籍(比如小说这类非常私人的东西,我甚至考虑以后专门建一个虚构类作品的 Calibre 数据库)。好在都有办法慢慢解决,例如,如果你已经习惯在 Calibre 中用标签整理书籍(Calibre 没有文件夹),可以尝试下面的脚本,仅导出匹配指定标签的书籍封面:
#!/usr/local/bin/python3
# -*- coding: UTF-8 -*-
import os
import sqlite3
import subprocess
import shutil
import re
tag_keyword = "关键词"
library_path = subprocess.check_output(
'/Applications/calibre.app/Contents/MacOS/calibre-debug -c "from calibre.utils.config import prefs; print(prefs.get(\'library_path\'), end=\'\')"',
shell=True
).decode('utf-8').strip()
metadata_db = os.path.join(library_path, 'metadata.db')
con = sqlite3.connect(metadata_db)
cur = con.cursor()
query = """
SELECT books.id, books.title, books.path
FROM books
WHERE books.id IN (
SELECT book FROM books_tags_link
WHERE tag IN (
SELECT id FROM tags WHERE name LIKE ?
)
)
"""
cur.execute(query, (f'%{tag_keyword}%',))
results = cur.fetchall()
downloads_path = os.path.join(os.path.expanduser('~'), 'Downloads')
target_dir = os.path.jo
