使用 Obsidian 的过程中发现一些痛点:
1> 使用插件 annotator 时,引用的文本内不能添加链接

如果强行将关键字改成链接,就会出现格式错乱

我想要的效果如下:引用的文本能够给添加链接

2> 使用简悦剪藏文章时 ,文章内的图片是外链。如果图片是外链,那么如果外链链接失效了就找不回图片了

我想要的效果如下:将外链转为图床的链接

3> Media Extended 虽然能使用链接跳转到视频指定时间点,但是手写链接太麻烦了

我想要的效果:自动生成一个视频的全部字幕和引用,且每句字幕和一点时间点引用呈一一对应的关系

❗❗❗为了节省大家的时间,我在文末写了我常用的 Obsidian 插件的下载链接,这些插件中有部分插件的源码都是被我修改过的,有两个需要注意的事项:
- 被我修改过源码的插件不全是可以直接用的,比如发送图片到 Picgo 时的 url 要换成你在 Picgo 内设置的 url
- 修改过源码的插件不要升级,如果升级了会导致修改失效
❗❗❗再附加一个注意事项:
- 入门简悦的时候如果运气不好(比如我),会遇到很多问题,如果看了入门文章还是解决不了就到 https://github.com/Kenshin/simpread/issues 提交自己的问题,简悦的开发者回复消息还是挺快的(顺便夸夸开发者很积极,这很难得)
修改 annotator 引用格式
使用工具
Annotator:是一个用于给 pdf 做标注和跳转的开源插件
Linter:是一个用于格式化笔记的开源插件
配置 Linter
目标:修改其源代码,添加将 Annotator 样式修改为 📌
添加规则分类:
添加具体的规则处理:
代码如下:
new Rule('Reform Annotation', '修改 Annotation 链接的样式', RuleType.MINE, (text, options = {}) => {
const searchRegex = new RegExp(`${options['Find']}`, "gm");
text = text.replace(searchRegex, `${options['Replace']}`);
return text;
},[
], [
new TextOption('Find', 'regular expression for finding', "\\[\\[([^@]+)@annote#([^|]+)\\|([^📌\\]]+)\\]\\]"),
new TextOption('Replace', 'result of replacement', '[[$1@annote#$2|📌]]$3')
])
保存后退出 ide,并重启 Obsidian,打开 Linter 的设置,并开启新功能的开关
测试一下:
(测试前)
(测试后)
外链图片转图床
使用工具
简悦(版本为 2.2):这是一个可以把网页文章保存为本地 markdown 格式笔记的工具
配置 Linter
修改 Linter 源代码,添加可以将图片外链转换成自己图床的链接
ps. 我使用的图床是 github,并且使用 PicGo 快速上传图片到图床。
首先引入 http、https、fs 库
var fs = require('fs');
var http = require('http');
var https = require('https');
添加自定义的方法
ps. 我的前端能力很菜很菜,献丑了。大佬们可以自己写这部分的功能。这段代码的逻辑就是先在本地新建一个文件夹 temp_pic_folder
,然后通过正则表达式匹配到图片链接,然后通过这些链接把图片一个一个下载到文件夹 temp_pic_folder
中,然后再把下载下来的图片一个一个上传到用户设置的图床中,并修改文章中这些图片链接为最终图床上的图片链接,最后再把文件夹 temp_pic_folder
及其内部的图片都从本地删了。
注意!我这段代码只能匹配末尾有图片文件后缀名的链接,如 https://xxxx/xxx/.../xxx.jpg
,如果是 https://xxxx/xxx/.../xxx
就不能识别,别问我为什么不处理这种情况,懒且够用
/**
* 将外链图片链接转换为自己的图床链接
*
* @param text 文档的文本
* @param options 插件在 setting tab 的设置
*/
async changeOuterLink (text, options = {}, app) {
// 获取当前编辑文档的所在文件系统的绝对地址
const tempFolder = "temp_pic_folder";
const anchorFile = "anchor_file";
const parentPath = app.vault.getAbstractFileByPath(app.workspace.getActiveFile().path).parent.path;
// 以 \ 为分隔符的就当作是 path const tempFolderRelativePath = parentPath + "\\" + tempFolder;
// 以 / 为分隔符的就当作是 url const tempFolderRelativeUrl = parentPath + "/" + tempFolder;
const anchorFileRelativeUrl = tempFolderRelativeUrl + "/" + anchorFile;
const basePath = app.vault.adapter.getBasePath();
const absolutePath = basePath + "\\" + tempFolderRelativePath;
// 匹配图片网址的正则表达式(已测网站图床包括:知乎、CSDN等)
const searchRegex = new RegExp(`${options['Image regex']}`, "gm");
// 自己图床的 url let myPicBedUrl = `${options['Host']}` + `${options['Repository']}`;
// 已经下载的文件的列表
let fileList = [];
// 文件名到其相应文件后缀的映射
let fileExtMap = {};
// 下载图片后返回的多个 promise let promiseList = [];
// 存放图片的文件夹里的文件列表
let tempFileSet = new Set();
let folderAbstractFile = app.vault.getAbstractFileByPath(tempFolderRelativeUrl);
if (folderAbstractFile) {
// 将文件名保存到 set 中
let downloadedPicList = app.fileManager.getNewFileParent(anchorFileRelativeUrl).children;
for (let i = 0; i < downloadedPicList.length; i++) {
tempFileSet.add(downloadedPicList[i].basename);
}
} else {
// 创建存放图片的文件夹
await app.vault.createFolder(tempFolderRelativePath);
folderAbstractFile = app.vault.getAbstractFileByPath(tempFolderRelativeUrl);
await app.vault.create(anchorFileRelativeUrl, "");
}
text = text.replace(searchRegex, (rs, $1, $2) => {
// 检查是否是自己图床的图片
if ($1.indexOf(myPicBedUrl) === -1) {
// 检查本地是否已经下载
let picName = $1.slice($1.lastIndexOf('/') + 1).toString();
if (!tempFileSet.has(picName)) {
let perPromise = new Promise((resolve, reject) => {
let request;
if ($1[4] === "s") {
request = https.get($1 + $2, (res) => {
if (res.statusCode !== 200) {
console.log("download image error!");
return; }
let ext = res.headers['content-type'].split('/')[1];
let dest = absolutePath + "\\" + picName + "." + ext;
let file = fs.createWriteStream(dest);
res.on('end', () => {
console.log("图片下载完毕");
});
// 进度、超时等
file.on('finish', () => {
file.close();
fileList.push(dest);
fileExtMap[picName] = ext;
resolve();
}).on('error', (err) => {
fs.unlink(dest);
});
res.pipe(file);
});
} else {
// todo http 的不管用
// 和 https 一样的代码
}
request.on('error', reject);
request.end();
});
promiseList.push(perPromise);
} else {
console.log("图片已存在");
}
} else {
console.log("自己的地址");
}
return rs;
});
return Promise.allSettled(promiseList)
.then((results) => {
console.log("文件列表 " + fileList);
if (fileList.length > 0) {
return new Promise((resolve, reject) => {
const postOptions = {
hostname: `${options['PicGoUrl']}`,
port: `${options['PicGoPort']}`,
path: `${options['PicGoPath']}`,
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}
const req = http.request(postOptions, async res => {
if (res.statusCode === 200) {
console.log("删除图片文件夹 " + folderAbstractFile.path);
await app.vault.delete(folderAbstractFile, true);
// 正则替换图片地址
text = text.replace(searchRegex, (rs, $1) => {
console.log("替换链接名");
// 根据文件名在 map 中找到对应的后缀
let picName = $1.slice($1.lastIndexOf('/') + 1);
let ext = fileExtMap[picName];
return ";
} else {
console.log("上传失败");
}
resolve(text);
});
req.on('error', error => {
console.error(error);
reject(error);
});
req.write(JSON.stringify({list: fileList}));
req.end();
});
} else {
return new Promise((resolve, reject) => {
resolve(text);
});
}
});
}
该方法调用处也要做修改:
因为 changeOuterLink
方法是异步的,所以调用该方法的地方要加上 await
然后在 rules 上添加新的规则
new Rule('Upload Image', 'Switch', RuleType.MINE, (text, options = {}, app, changeOuterLink) => {
return changeOuterLink(text, options, app);
},[
], [
new TextOption('PicGoUrl', '', "127.0.0.1"),
// PicGoPort 是 picgo 默认的端口号
new TextOption('PicGoPort', '', "36677"),
new TextOption('PicGoPath', '', "/upload"),
new TextOption('Host', 'target host', "https://raw.githubusercontent.com"),
// Repository 为 github 的仓库名
new TextOption('Repository', '', "/xxx/xx/"),
new TextOption('Image regex', '', '\\!\\[[^\\[\\]]*\\]\\(([a-zA-z]+://[^/]+/[^.?)]+)([^)?]*)')
])
根据字幕跳转视频
使用工具
Media Extended:使用这个插件能够播放网络视频并能跳转进度条(我已测试能行的网站有 youtube,其他没试过)
Media Extended BiliBili Plugin:这个插件是支持播放 bilibili 视频并能跳转进度条的插件
Regex Find and Replace:该插件可用于正则表达式查找和替换
Language Reactor:这是个 chrome 插件,可以在看 youtube 的时候把英文字幕翻译成中文(或其他语言),并且能选中字幕中的单词查看释义甚至可以把单词加入生词库,在生词库可以使用类似 anki 的复习方式背诵单词
安装 chrome 插件
设置 Regex Find and Replace
修改该插件的源代码
// 匹配 xxs
let regDocumentText = documentText.replace(/(^[0-5]?[0-9])s$/gm, (rs, $1) => {
return ">👇[" + $1 + "s](" + replaceString + "#t=" + $1 + ")";
});
// 匹配 mm:ssregDocumentText = regDocumentText.replace(/(^[0-5]?[0-9]):([0-5][0-9])$/gm, (rs, $1, $2) => {
return ">👇[" + $1 + ":" + $2 + "](" + replaceString + "#t=" + ($1 * 60 + $2 * 1) + ")";
});
// 匹配 hh:mm:ssregDocumentText = regDocumentText.replace(/((^[0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))$/gm, (rs, $1, $2, $3) => {
return ">👇[" + $1 + ":" + $2 + ":" + $3 + "](" + replaceString + "#t=" + ($1 * 60 * 60 + $2 * 60 + $3 * 1) + ")";
});
editor.setValue(regDocumentText);
第一个正则表达式 ([0-5]?[0-9])s$
是转换 xxs\n
格式的字符串
第二个正则表达式 ([0-5]?[0-9]):([0-5][0-9])
是转换 m:ss
或 mm:ss
格式的字符串
第三个正则表达式 (([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))$
是转换 hh:mm:ss
格式的字符串
以上统一最终转换为 >👇[$1s](网址#t=$1)
之所以要设置以上正则,是为了匹配 Language Reactor 生成的字幕文件,下面工作流会介绍到
然后设置一下该插件的快捷键:
导出视频的中英字幕
先打开 Language Reactor
一开始打开某个视频可能默认是中文字幕,要改成英文的:
点击 设置 查看字幕情况:
(一般没有人类翻译的话相应的单选框是选择不了的)
因为根据上图可以知道这个视频是有人类翻译的,所以导出字幕时可以仅仅导出英文字幕和人类翻译:
接着按 导出 就会弹出字幕的 html:
接着 ctrl + a
全选整个 html 并且 ctrl + c
复制:
新建新的关于这个视频笔记的 markdown 并插入自己设置好的视频模板(其实就只是一些元数据):
接着在这个 markdown 里 ctrl + v
一下,可以看到复制进来了,并且把选中的这三行没用的信息删了:
使用正则替换修改字幕笔记的格式
复制一下视频的地址:
回到刚刚新建的笔记,按 ctrl + alt + f
进行正则替换:
可以看到替换成功了 👇
注意:想跳转视频一定要切换到 查看视图
结尾
这篇文章末尾就不写参考了,因为纯纯是我的经验分享,如有雷同是他抄我的。
该给的链接都给了,希望大家不要忘了给那些开源插件点个 star 哦~
我常用的 Obsidian 插件链接:
链接:https://pan.baidu.com/s/1i-eKm4UdqXAigvwUEQn8QQ
提取码:oyac
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!