关于使用QuickAdd脚本实现按创建时间归档Obsidian附件的方案

:warning:实验性脚本,对库的变动较大,谨慎使用,本文旨在为有需求的人提供方法

Obsidian的附件管理一直是个难题。我目前采用指定的附件文件夹来管理附件,并尽可能简短地使用WIKI链接。然而,随着笔记的增长,附件文件夹里已经有七千多张图片,每次打开都令人头疼。

我考虑采用PKMer群友之前讨论的,按照YYYY/YYYY-MM-DD的方式归档附件。但在Obsidian的插件市场中,似乎没有直接这个方法的附件管理插件。因此,我尝试使用QuickAdd脚本,以附件的创建时间为基础,进行文件夹分类归档,调用的是Obsidian自带的API进行文件移动,这样,附件链接会自动更新。实现效果如下:

2024-06-13_055036 关于使用QuickAdd脚本实现按创建时间归档Obsidian附件的方案_IMG-2

脚本的配置选项:

2024-06-13_055036 关于使用QuickAdd脚本实现按创建时间归档Obsidian附件的方案_IMG-3

  • 自定义文件夹:为空则为Obsidian 默认设置,也可以自定义路径
    • PS: 针对该文件夹路径下(不包含子文件夹)的符合**文件类型(file type)**的文件进行归档。
  • 归档日期格式YYYY/YYYY-MM-DD,可以预览
    • 2024-06-13_055036 关于使用QuickAdd脚本实现按创建时间归档Obsidian附件的方案_IMG-2
  • 文件类型:默认为:png|jpe?g|webp|mp[3,4]|pdf
    • 文件后缀类型,不同类型用|分隔,不区分大小写。
  • 自启动设置:可以在 QuickAdd 的 Macro Manager 设置自启动。

QuickAdd Macro 脚本

脚本配置流程:

按创建时间归档附件.js
/*
 * @Author: 熊猫别熬夜 
 * @Date: 2024-06-13 07:04:22 
 * @Last Modified by: 熊猫别熬夜
 * @Last Modified time: 2024-06-22 23:51:47
*/

module.exports = {
  entry: async (QuickAdd, settings, params) => {
    // 获取默认配置
    const fs = require('fs');
    const path = require('path');
    // 获取库的基本路径
    const basePath = (app.vault.adapter).getBasePath();

    // 日期格式
    const dateFormat = settings["归档日期格式(date format)"].replace("{{DATE:", "").replace("}}", "");

    // 获取设置的指定附件文件夹路径:
    // const assetsPath = "900【素材】Assets/910_ObsidianAssets";
    let assetsPath = app.vault.config.attachmentFolderPath;
    if (settings["自定义文件夹路径(custom folder path)"]) {
      assetsPath = settings["自定义文件夹路径(custom folder path)"];
    }

    const assetsPathFull = basePath + "/" + assetsPath;
    console.log(assetsPathFull);

    // 获取该路径下所有附件:
    const assetsList = fs.readdirSync(assetsPathFull).filter(file => {
      const ext = path.extname(file).toLowerCase();
      // 使用正则表达式来匹配文件扩展名:
      // 如果 settings["文件类型(file type)"] 为空值,则匹配所有文件类型
      const regexMatch = new RegExp("\\.(" + (settings["文件类型(file type)"] || ".+") + ")$", "i");
      return regexMatch.test(ext);
    }).map(file => assetsPath + "/" + file);

    console.log(assetsList);

    // 批量获取创建日期并用ob的API移动附件
    for (const filePath of assetsList) {
      const ctime = app.vault.getAbstractFileByPath(filePath).stat["ctime"];
      const formattedDatePath = assetsPath + "/" + moment(ctime).format(dateFormat);
      console.log(formattedDatePath);
      if (!await app.vault.getFolderByPath(formattedDatePath)) {
        await app.vault.createFolder(formattedDatePath);
      }

      const destinationPath = path.join(formattedDatePath, path.basename(filePath));
      await app.fileManager.renameFile(app.vault.getAbstractFileByPath(filePath), destinationPath);
    }
    new Notice(`🔊${assetsList.length}个附件归档已完成`);
  },
  settings: {
    name: "按创建时间归档Obsidian附件",
    author: "熊猫别熬夜",
    options: {
      "自定义文件夹路径(custom folder path)": {
        type: "text",
        defaultValue: "",
        description: "如果为空则为Obsidian默认附件存放路径"
      },
      "归档日期格式(date format)": {
        type: "format",
        defaultValue: "{{DATE:YYYY/YYYY-MM/YYYY-MM-DD}}",
        description: "如果想以文件类型分类,可以配置{{DATE:[图形文件]YYYY/YYYY-MM-DD}}、{{DATE:[视频文件]YYYY/YYYY-MM-DD}}",
      },
      "文件类型(file type)": {
        type: "text",
        defaultValue: "png|jpe?g|webp|gif|mp[34]|pdf",
        description: "文件后缀类型,不同类型用|分隔,不区分大小写,如果为空值则默认全部附件。"
      }
    }
  }
};

拓展方法:Python 附件归档

不过需要注意的是,QuickAdd的方法一次性只能处理少量附件,要处理大量附件的话可能会需要用很长时间,除了使用 QuickAdd 之外,还可以通过 Python 脚本实现附件归档。但该方式是在库外移动文件位置,这样会导致链接失效。如果您的库全部使用的是简短的 WIKI 链接格式,这种方法可以初步更快地进行归档,在修改后重启 Obsidian 就能修复,之后可以采用第一种方法。

:orange_circle:Python脚本是库外移动文件,会导致链接失效,不推荐使用

保存为 00_ObsidianAssets.py 存放在 Obsidian 默认附件文件夹。

00_ObsidianAssets.py
import os
import shutil
from datetime import datetime

# 获取当前文件夹路径
current_dir = os.getcwd()

# 遍历当前文件夹下的所有文件
for file_name in os.listdir(current_dir):
    if file_name.lower().endswith(('.jpg', '.jpeg', '.png', '.pdf')):
        file_path = os.path.join(current_dir, file_name)
        # 获取文件的创建日期
        create_time = datetime.fromtimestamp(os.path.getctime(file_path))
        folder_name = create_time.strftime('%Y/%Y-%m/%Y-%m-%d')
        folder_path = os.path.join(current_dir, folder_name)

        # 创建文件夹
        os.makedirs(folder_path, exist_ok=True)

        # 移动文件到对应的文件夹
        shutil.move(file_path, os.path.join(folder_path, file_name))

可以设置 .bat 进行运行:

@echo off
python "%cd%\00_ObsidianAssets.py"
exit
2 个赞

可以在库外调用obsidian的api移动,比如,我想到的方法是通过注册registerObsidianProtocolHandler回调处理文件的移动。
然后库外可以这样调用obsidian://move-file?vault=vault&file=file&to=path

1 个赞

可以用 Paste image rename 插件,可以带路径重命名图片附件,支持日期。随时分类存放。

抱歉,我从插件市场下的,如果设置附件是{{DATE:YYYY-MM-DD}}/{{fileName}}的话,它显示我无法进行重命名,因为里面包含/,请问该如何进行设置

这个插件只是重命名,不能新建文件夹,也就是说你需要先把文件夹建好。按照楼主按年归档,需要手动每年建个文件夹。
我的配置是按照时间戳命名图片:Images/{{DATE:YYYY/x}}

哦哦,原来如此,非常感谢解答,可能这个插件与我想要实现的效果稍微有点不一样,最好是年月日嵌套的格式

不过其实也算可以实现,直接用脚本在每次启动ob时创建对应文件夹,剩余的交给Paste image rename 插件也是可以的,我发布的这个QuickAdd脚本可以用来整理之前没整理的图片,让他们自动归档,可能像Canvas,Excalidraw里面的图片也无法用Paste image rename 进行归档,这个QuickAdd脚本也可以让它归档。

我现在用的是Attachment Name Formatting插件对附件进行重命名,主要以笔记名进行重命名,有时候图片的名称可以让我定位到笔记,时间戳格式的命名反而让我有点迷糊,虽然可以通过反向链接去识别,但还是笔记名对附件重命对我来说要好点。

似乎附件文件夹也需要一个类似日记路径的解析方式 :joy:


看了一下,两年前就有这样的需求了()

1 个赞

yep,如果官方能直接加入这个解析功能就好了

说起来我之前改过 GitHub - trganda/obsidian-attachment-management: Attachment Management of Obsidian
让它支持/,应该可以用类似的思路来

// calmwaves
renameCreateFile(attach, attachPath, attachName, source) {
  const dst = (0, import_obsidian10.normalizePath)(path.join(attachPath, attachName));
  //console.log("测试dst"+dst) // calmwaves
  debugLog("renameFile - ", attach.path, " to ", dst);
  const original = attach.basename;
  const name = attach.name;
  // 创建目标路径中的目录
  this.createDirectoriesForPath(dst).then(() => {
    // 目录创建完成后,重命名文件
    this.app.fileManager.renameFile(attach, dst).then(() => {
      new import_obsidian10.Notice(`Renamed ${name} to${attachName}.`);
    }).finally(() => {
      const { setting } = getOverrideSetting(this.settings, source);
      MD5(this.app.vault.adapter, attach).then((md5) => {
        saveOriginalName(this.settings, setting, attach.extension, {
          n: original,
          md5
        });
        this.plugin.saveData(this.settings);
      });
    });
  });
}

// 新增的函数,用于创建目标路径中的目录
createDirectoriesForPath(filePath) {
  return new Promise((resolve, reject) => {
    const directory = path.dirname(filePath);
    this.app.vault.adapter.exists(directory).then(exists => {
      if (exists) {
        resolve();
      } else {
        this.app.vault.createFolder(directory).then(() => {
          resolve();
        }).catch(error => {
          reject(error);
        });
      }
    });
  });
}
2 个赞

哦哦 厉害了 有时间研究下 终究上升到了DIY插件 :joy: