# 巴别图书馆计划（十一）：书架作屏保，藏书触手及

> 本文开放前 70% 正文，剩余内容会员可见。

原文：https://utgd.net/article/21112

闲来无事，我总喜欢泡在图书馆，倒不是喜欢里面的气氛（一群学生绷着神经备考的酸味令人作呕），而是喜欢书架。书总是看不完的，而实体书架有重要的浏览式查询功能（于良芝语），你可以在图书室里随意走动（所以我不喜欢那些考生，好像裤脚的摩擦声都会导致他们落榜一样），摸索书脊，偶若觉得哪一本有趣，就随手拾起或抽出。

易言之，实体书架提供了逛的功能，大概电子设备很难模仿（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
```

---

[前往标准阅读页](https://utgd.net/article/21112?audience=web)
