Scanした紙のノートをScrapboxに取り込むUserScript
裁断した書籍にも使える
使い方
pdfファイルをuploadすると、scrapboxにimportできるjson fileを生成してdownloadする
pdfの解像度を指定できる
初期値は600
600より大きい値を指定する意味はない
また600だとuploadが非常に遅くなるので注意すること
手書きメモをuploadするときには600くらい必要
400か500でもいいかもしれないが、未確認
デジタル形式のPDF (講義ノートとか)であれば、150で十分
150と300とでほとんど差がない
実装
pdfの読み込みとGyazoへのupload
一つづつawaitで待ってuploadするようにした
uploadの順番と、ページ番号の順番とを一致させたい
2021-01-28 00:02:26 ……これ一致させる必要あるかな?
全部一斉に送っちゃえ!
json fileにしてdownloadする
問題はレイアウトか。
1 pageごとにScrapbox pageに分ける
タイトル
PDFの名前 + ページ番号
\`${title} ${pageNum}\`でいいか。
メタ情報をどうやって入れる?
PDFのテキストを勝手にScrapboxに挿入できるようにするととても楽
2021-01-24 19:28:40 方法が見つからないので断念
とりあえずタイトルにノート名を入れるとして
ノート名+ページ番号
前後のページに対するリンクも貼っておく?
ノート名をhashtagでつけるだけでもいいのでは?
でも前後のリンクもあると読みやすいかtakker.icon
じゃあつけるか。
→cf.xxxのリンクが貼ってあったら、そのページへのリンクも手動で書く 2021-02-15
23:12:54 PDFの中にテキストが含まれていたら、それを引用形式で書き出すようにした
2021-01-29
19:14:27 解像度を指定できるようにした
2021-01-25
23:42:21
更新・作成日時を秒単位に直して代入するようにした
一部の関数を別ページに切り出した
known issue
Gyazo accountと結びつかない?
変だなtakker.icon
/icons/done.icon解像度が粗い?
デジタル作成したPDFは問題ない
scanしたPDFの解像度が粗くなっている?
これを試してみよう。
解決したが、逆にupload速度が落ちてしまった
途中でCSPエラーが発生して失敗する
大きなPDFをuploadしているときに起きる
失敗したページをretryする機能が必要
作業ログ
いや、切り出すときにprivateかpublicかの判断ができないから難しいか。
dependencies
code:script.js
/* MIT License Copyright (c) 2020 ci7lus */
import '../use-pdfjs/script.js';
import '../PDFをGyazoにあげてScrapboxに取り込むUserScript/progressBar.js';
import {toArrayBuffer} from '../Blob.arrayBuffer()/script.js';
import {pdfPage2ImageDataURI} from '../pdfPage2ImageDataURI/script.js';
import {uploadToGyazoEasyAuth} from '../uploadToGyazoEasyAuth/script.js';
import {getLocalFiles} from '../簡単にfileをbrowserに取り込むscript/script.js';
import {downloadObject} from '../web_browserから任意のデータをdownloadするscript/script.js';
import {throttle} from '../promise-parallel-throttle/script.js';
scrapbox.PageMenu.addMenu({
title: 'upload-pdf',
onClick: async () => {
const file = await getLocalFiles({accept: 'application/pdf,.pdf'});
if (!file) return;
console.log(file);
const resolution = (()=>{
let value = prompt('Type the print resolution.', 600);
if (value === null) return value;
while (isNaN(parseInt(value))) {
value = prompt('Invalid number.\nType the print resolution.', 600);
if (value === null) return value;
}
return parseInt(value);
})();
if (resolution === null) return;
// 進捗表示エリアを作る
const progressArea = document.createElement('progress-area');
document.body.appendChild(progressArea);
const imageDataList = await pdf2GyazoDataList(file, resolution, progressArea);
progressArea.message = 'Create json data...';
const pageData = {pages: imageDataList.map(({imageURL,text}, index) => {
index++;
const title = ${file.name} ${index};
const nextLink = index === imageDataList.length ? '': [${file.name} ${index + 1}];
const previousLink = index === 1 ? '': [${file.name} ${index - 1}];
const quotes = text.trim() !== '' ?
text.split(/\n/).flatMap(line => {
if (/^\s*$/.test(line)) return [];
return [> ${line.replace(/(?<!\w)\s+(?=\W)/g, '')}];
}) : [];
return {
title,
created: file.lastModified / 1000,
updated: file.lastModified / 1000,
lines: [
title,
[[${imageURL}]],
<=${previousLink} | ${nextLink}=>,
...quotes,
],
};
}),};
downloadObject(pageData);
progressArea.message = 'Done.';
document.body.removeChild(progressArea);
},
});
PDFを読み込み、Gyazoに順番を維持してuploadする関数
code:script.js
async function pdf2GyazoDataList(pdfFile, printResolution, progressArea) {
PDFを読み込む
code:script.js
progressArea.message = 'Loading pdf file...';
try {
const pdf = await pdfjsLib.getDocument({
data: await toArrayBuffer(pdfFile),
// cors制限のためcmapsはstorage.googleapis.comに上げたものを用いる
cMapUrl:
cMapPacked: true,
}).promise;
const metadata = await pdf.getMetadata();
console.log(metadata);
progressArea.message = 'pdf file loaded. Uploading to gyazo...';
code:script.js
let progress = 0;
const updateProgressText = () => {
progressArea.message = `Uploading... (${progress++}/${
pdf.numPages
})`;
};
updateProgressText();
Gyazoにuploadする
10ページづつ一気に送る
ページ番号は1から始まるようだ
code:script.js
let promise = undefined;
const uploadCallback = i => (async () => {
console.log([page ${i + 1}] Converting a pdf page to a png image...);
const page = await pdf.getPage(i + 1);
const textPromise = page.getTextContent();
const dataURI = await pdfPage2ImageDataURI(page, {printResolution});
console.log([page ${i + 1}] Converted. Uploading an image to Gyazo...);
try {
const imageURL = await uploadToGyazoEasyAuth({
dataURI,
title: pdfFile.name,
clientId: 'fd84cef882b9b51d8de3365cd28c86bc2cc8a1646ef05dbc641ca49278f9d0d6',
});
console.log([page ${i + 1}] Uploaded: ${imageURL});
updateProgressText();
PDF中のテキストを取得する
改行を判定できないので、全て1行で取得する
code:script.js
const tokenizedText = await textPromise;
return {imageURL, text: tokenizedText.items?.map?.(pageText => pageText.str)?.join?.('') ?? ''};
} catch(e) {
console.error([page ${i + 1}] Failed to upload the page. , e);
console.log([page ${i + 1}] Will be tried to upload later.);
return undefined;
}
});
.map(i => uploadCallback(i)), {maxInProgress: 10});
uploadに失敗したページを再uploadする
code:script.js
while (imageDataList.some(data => data === undefined)) {
imageDataList = await throttle(imageDataList.map((data, i) => async () => {
if (data !== undefined) return data;
console.log([page ${i + 1}] Retry to upload.);
return await uploadCallback(i)();
}), {maxInProgress: 10});
}
progressArea.message = 'done';
return imageDataList;
} catch (e) {
console.error('failed', pdfFile, e);
alert(failed to load: ${pdfFile.name});
}
}
補助関数
指定した時間だけ待つ
code:script.js
const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));