2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Obsidianで自作プラグイン作成シリーズ 第一弾(Paste Image as WebP)

Last updated at Posted at 2025-11-30

はじめに

Obsidianで画像を多用していると、Vaultのサイズがどんどん膨れ上がっていく経験はないでしょうか。特にスクリーンショットを頻繁に貼り付ける場合、PNGやJPEG形式の画像ファイルがストレージを圧迫します。

この問題を解決するため、クリップボードから画像をペーストする際に自動的にWebP形式に変換するObsidianプラグイン「Paste Image as WebP」を開発しました。本記事では、WebP形式の利点とプラグインの機能、そして開発の過程について解説します。

WebP形式の利点

WebPは、Googleが開発した画像フォーマットで、従来のPNGやJPEGと比較して以下のような優位性があります。

ファイルサイズの削減

WebPの最大の特徴は、高い圧縮率です。同程度の画質を保ちながら、PNGやJPEGと比較して20〜50%程度ファイルサイズを削減できます。

具体的な例を挙げると、4096×4096ピクセルのスクリーンショットの場合:

  • PNG形式:約8〜12MB
  • JPEG形式(品質85%):約2〜4MB
  • WebP形式(品質85%):約1〜2MB

この差は、画像を多用するVaultでは大きな影響を与えます。

透過対応

WebPは、PNG同様にアルファチャンネル(透過)をサポートしています。そのため、背景が透明な画像もそのまま扱うことができます。

ブラウザ対応

現在、主要なブラウザはすべてWebPをサポートしており、Obsidianでも問題なく表示できます。

プラグインの機能

基本機能

「Paste Image as WebP」プラグインは、以下の基本機能を提供します。

  1. 自動変換:クリップボードから画像をペースト(Ctrl+V / Cmd+V)すると、自動的にWebP形式に変換して保存
  2. エディタへの自動挿入:変換後の画像を![[ファイル名.webp]]形式で自動的にエディタに挿入
  3. 重複ファイルの処理:同名ファイルが存在する場合、自動的に連番を追加(例:image-1.webp, image-2.webp

ファイル名のカスタマイズ

ファイル名は2つの形式から選択できます。

固定名形式

同じファイル名で保存します。例えば、image.webpという固定名を設定すると、すべての画像がこの名前で保存されます(重複時は自動的に連番が追加されます)。

タイムスタンプ形式

日時をベースにしたファイル名を生成します。フォーマットは自由にカスタマイズ可能で、以下のプレースホルダーを使用できます。

  • YYYY:年(4桁)
  • MM:月(2桁)
  • DD:日(2桁)
  • HH:時(2桁、24時間形式)
  • mm:分(2桁)
  • ss:秒(2桁)

デフォルトのYYYYMMDDHHmmss形式では、20231116143025.webpのようなファイル名が生成されます。カスタム例として、YYYY-MM-DD_HHmmssとすると2023-11-16_143025.webpという形式になります。

保存先の設定

画像の保存先は3つのオプションから選択できます。

  1. 現在のノートと同じフォルダ:ノートと同じディレクトリに指定したフォルダ名(デフォルト:attachments)を作成して保存
  2. Vaultルート:Vaultのルートに指定したフォルダを作成して保存
  3. カスタムパス:任意のパスを指定(例:project/images

WebP品質の調整

WebPの品質は0.1〜1.0の範囲で調整できます。デフォルトは0.85で、ファイルサイズと画質のバランスが取れた値です。

  • 1.0:最高品質(ファイルサイズ大)
  • 0.85:推奨値(バランス型)
  • 0.5〜0.7:低品質(ファイルサイズ重視)

セキュリティ機能

プラグインには、以下のセキュリティ対策が実装されています。

  1. パストラバーサル攻撃の防止:ファイル名とフォルダパスをサニタイズし、Vault外部への書き込みを防止
  2. DoS攻撃の防止:画像の最大ピクセル数(デフォルト:16,777,216 = 4096×4096)と最大ファイルサイズ(デフォルト:10MB)を制限
  3. ファイル名インジェクションの防止:特殊文字を適切に処理
  4. 無限ループの防止:重複ファイル名チェックの試行回数を制限

これらの設定は、プラグインの設定画面から変更できます。

技術的な実装

開発環境

プラグインは以下の技術スタックで開発しました。

  • 言語:TypeScript
  • ビルドツール:esbuild
  • ターゲット環境:Obsidian Plugin API

プロジェクト構成

obsidian_extension/
├── main.ts              # メインのプラグインロジック
├── manifest.json        # プラグインのメタデータ
├── package.json         # npm設定
├── tsconfig.json        # TypeScript設定
├── esbuild.config.mjs   # esbuild設定
└── styles.css           # スタイルシート

TypeScriptからJavaScriptへのビルド

Obsidianプラグインは、最終的にJavaScriptファイル(main.js)として配布する必要があります。開発ではTypeScriptを使用し、esbuildでバンドル・トランスパイルしています。

package.jsonのスクリプト設定:

{
  "scripts": {
    "dev": "node esbuild.config.mjs",
    "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production"
  }
}

開発時はnpm run devでファイル変更を監視し、自動的にリビルドされます。本番ビルドはnpm run buildで実行します。

esbuildの設定

esbuildは高速なJavaScriptバンドラーで、TypeScriptのトランスパイルも行えます。設定ファイル(esbuild.config.mjs)では、以下を指定しています。

{
  entryPoints: ["main.ts"],
  bundle: true,
  external: ["obsidian", "electron", ...],
  format: "cjs",
  target: "es2018",
  outfile: "main.js"
}

重要なポイント:

  • external:Obsidianが提供するモジュールは外部依存として扱う
  • format: "cjs":CommonJS形式で出力
  • target: "es2018":ES2018をターゲットに設定

画像変換のコアロジック

画像の変換処理は、ブラウザのCanvas APIを使用しています。

private async convertToWebP(file: File): Promise<Blob> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const reader = new FileReader();

    reader.onload = (e) => {
      img.onload = () => {
        // 画像サイズの検証
        this.validateImageDimensions(img.width, img.height);

        // Canvasに描画
        const canvas = document.createElement('canvas');
        canvas.width = img.width;
        canvas.height = img.height;
        
        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0);

        // WebPに変換
        canvas.toBlob(
          (blob) => {
            if (blob) {
              resolve(blob);
            } else {
              reject(new Error('Failed to convert to WebP'));
            }
          },
          'image/webp',
          this.settings.webpQuality
        );
      };

      img.src = e.target?.result as string;
    };

    reader.readAsDataURL(file);
  });
}

この実装のポイント:

  1. FileReaderでファイルをData URLとして読み込み
  2. Image要素にロード
  3. Canvasに描画
  4. canvas.toBlob()でWebP形式に変換(品質指定可能)

ファイル保存処理

Obsidian Vault APIを使用してファイルを保存します。

private async saveImage(blob: Blob, filename: string, view: MarkdownView): Promise<string> {
  // フォルダパスの決定
  let folder: string;
  
  if (this.settings.imageFolderLocation === 'current-folder') {
    const currentFile = view.file;
    const baseFolder = currentFile?.parent?.path || '';
    folder = baseFolder ? `${baseFolder}/${folderName}` : folderName;
  }
  // ... その他のロケーション処理

  // フォルダが存在しない場合は作成
  const folderExists = await this.app.vault.adapter.exists(folder);
  if (!folderExists) {
    await this.app.vault.createFolder(folder);
  }

  // 重複チェックと保存
  let filepath = `${folder}/${filename}`;
  let counter = 1;
  
  while (await this.app.vault.adapter.exists(filepath)) {
    const nameWithoutExt = filename.replace('.webp', '');
    filepath = `${folder}/${nameWithoutExt}-${counter}.webp`;
    counter++;
  }

  // ArrayBufferに変換して保存
  const arrayBuffer = await blob.arrayBuffer();
  await this.app.vault.createBinary(filepath, arrayBuffer);

  return filepath;
}

クリップボードイベントのハンドリング

Obsidianのエディタペーストイベントをインターセプトします。

async onload() {
  await this.loadSettings();

  this.registerEvent(
    this.app.workspace.on('editor-paste', this.handlePaste.bind(this))
  );

  this.addSettingTab(new PasteImageAsWebPSettingTab(this.app, this));
}

private async handlePaste(evt: ClipboardEvent, editor: Editor, view: MarkdownView) {
  const files = evt.clipboardData?.files;
  if (!files || files.length === 0) {
    return; // 画像がない場合は通常のペースト処理
  }

  const imageFiles = Array.from(files).filter(file =>
    file.type.startsWith('image/')
  );

  if (imageFiles.length === 0) {
    return;
  }

  evt.preventDefault(); // デフォルトのペースト動作を防ぐ

  for (const file of imageFiles) {
    await this.processImage(file, editor, view);
  }
}

インストール方法

コミュニティプラグインから(現在リリース申請中です)

プルリクエストが通ったら、コミュニティプラグインからインストールできます。
今しばらくお待ちくださいませ (+_+)

手動インストール

  1. GitHubリリースページからmain.jsmanifest.jsonstyles.cssをダウンロード
  2. Vaultのプラグインフォルダにpaste-image-as-webpフォルダを作成

  1. ダウンロードしたファイルを配置

  2. Obsidianを再起動してプラグインを有効化

使い方

  1. プラグインを有効化
  2. 設定画面で好みに合わせてカスタマイズ
  3. 画像をクリップボードにコピー
  4. Obsidianのエディタでペースト(Ctrl+V / Cmd+V)
  5. 自動的にWebP形式で保存され、エディタに挿入される

まとめ

「Paste Image as WebP」プラグインを使用することで、以下のメリットが得られます。

  1. Vaultのサイズ削減:画像ファイルサイズが20〜50%削減され、ストレージを節約
  2. シームレスな体験:通常のペースト操作と変わらない使い勝手
  3. 柔軟な設定:ファイル名、保存先、品質を自由にカスタマイズ可能
  4. セキュリティ:各種攻撃に対する防御機能を実装

画像を多用するObsidianユーザーにとって、このプラグインはVault管理の負担を大きく軽減できるツールとなるはずです。

GitHubでオープンソースとして公開していますので、バグ報告や機能要望はIssuesでお待ちしています。

リンク

補足

基本的な使い方

1. プラグインの有効化

  1. Obsidianの設定を開く(歯車アイコン)
  2. 左サイドバーから「コミュニティプラグイン」を選択
  3. 「Paste Image as WebP」を探して「有効化」をクリック

2. 画像の貼り付け手順

  1. 任意の画像をクリップボードにコピー
    • スクリーンショットを撮る(Windows: Win + Shift + S、Mac: Cmd + Shift + 4
    • ブラウザや画像ビューアーで画像を右クリック→「コピー」
    • 画像ファイルをコピー(Ctrl + C / Cmd + C
  2. Obsidianのエディタでペースト
    • Ctrl + V(Windows/Linux)
    • Cmd + V(Mac)
  3. 自動的に以下が実行される
    • 画像がWebP形式に変換
    • 指定したフォルダに保存
    • エディタに![[ファイル名.webp]]形式で挿入
    • 通知メッセージが表示される

設定項目の詳細

プラグインの設定画面は、「設定」→「コミュニティプラグイン」→「Paste Image as WebP」の歯車アイコンから開けます。

ファイル名設定(Filename format)

Filename format(ファイル名形式)

デフォルト値: Timestamp(タイムスタンプ)
選択肢:

  • Fixed name(固定名):毎回同じファイル名で保存
  • Timestamp(タイムスタンプ):日時ベースのファイル名
    設定方法:
    ドロップダウンメニューから選択します。

Fixed filename(固定ファイル名)

デフォルト値: image
適用条件: Filename formatが「Fixed name」の場合のみ表示
動作:

  • 設定した名前で保存される(例:image.webp
  • 同名ファイルが存在する場合、自動的に連番が追加される
    • 1枚目:image.webp
    • 2枚目:image-1.webp
    • 3枚目:image-2.webp
      設定方法:
  1. Filename formatを「Fixed name」に変更
  2. テキストフィールドに好きな名前を入力(拡張子は不要)
    使用例:
設定値: screenshot
結果: screenshot.webp, screenshot-1.webp, screenshot-2.webp...

Timestamp format(タイムスタンプフォーマット)

デフォルト値: YYYYMMDDHHmmss
適用条件: Filename formatが「Timestamp」の場合のみ表示
使用可能なプレースホルダー:

  • YYYY:年(4桁)例:2024
  • MM:月(2桁)例:01, 12
  • DD:日(2桁)例:01, 31
  • HH:時(2桁、24時間形式)例:00, 23
  • mm:分(2桁)例:00, 59
  • ss:秒(2桁)例:00, 59
    設定方法:
  1. Filename formatを「Timestamp」に変更
  2. テキストフィールドにフォーマットを入力
  3. リアルタイムでサンプルが表示される
    フォーマット例:
    | フォーマット | 出力例 | 用途 |
    |------------|--------|------|
    | YYYYMMDDHHmmss | 20241130143025.webp | デフォルト、秒単位で一意 |
    | YYYY-MM-DD_HHmmss | 2024-11-30_143025.webp | 読みやすい形式 |
    | YYYYMMDD_HH-mm-ss | 20241130_14-30-25.webp | 時刻を区切る |
    | YYYY-MM-DD | 2024-11-30.webp | 日付のみ(1日に1枚のみの場合) |
    | YYYYMMDD-HHmm | 20241130-1430.webp | 分単位(秒は不要な場合) |
    サンプル表示:
    設定画面でフォーマットを入力すると、下部に現在時刻でのサンプルが表示されます。
Sample: 20241130143025.webp

保存先設定

Image folder location(画像フォルダの場所)

デフォルト値: Same folder as current note(現在のノートと同じフォルダ)
選択肢:

  1. Same folder as current note:現在のノートと同じフォルダ
  2. Vault root:Vaultのルート
  3. Custom path:カスタムパス
    設定方法:
    ドロップダウンメニューから選択します。
    各選択肢の動作:
1. Same folder as current note(推奨)

現在編集中のノートと同じフォルダに、指定したサブフォルダを作成して保存します。
:

Vaultの構成:
├── Projects/
│   └── Project-A.md  ← 現在編集中
└── Daily/
    └── 2024-11-30.md
設定: Image folder name = "attachments"
保存先:
Projects/attachments/20241130143025.webp

メリット:

  • ノートと画像が近くに配置される
  • プロジェクトごとに画像を整理できる
  • ノートを移動しても相対パスが維持される

2. Vault root

Vaultのルートに、指定したフォルダを作成して保存します。
:

Vaultの構成:
├── Projects/
│   └── Project-A.md  ← 現在編集中
└── attachments/      ← ここに保存される
    └── 20241130143025.webp

メリット:

  • すべての画像が1箇所に集約される
  • 画像の一括管理が容易
    デメリット:
  • ノート数が多いと画像フォルダが巨大になる

3. Custom path

任意のパスを指定して保存します。
:

設定: Custom folder path = "resources/images"
保存先:
resources/images/20241130143025.webp

メリット:

  • 完全に自由なフォルダ構造を実現
  • 複数レベルのフォルダを指定可能

Image folder name(画像フォルダ名)

デフォルト値: attachments
適用条件: Image folder locationが「Custom path」以外の場合に表示
設定方法:
テキストフィールドにフォルダ名を入力します。
動作:

  • フォルダが存在しない場合、自動的に作成される
  • 既存のフォルダがある場合、そこに保存される
    :
設定値: images
結果: 
- Same folder as current noteの場合: ノートのフォルダ/images/
- Vault rootの場合: Vaultルート/images/

Custom folder path(カスタムフォルダパス)

デフォルト値: project/images
適用条件: Image folder locationが「Custom path」の場合のみ表示
設定方法:

  1. Image folder locationを「Custom path」に変更
  2. テキストフィールドに完全なパスを入力
    パス指定の規則:
  • Vaultルートからの相対パス
  • /(スラッシュ)で区切る
  • 先頭や末尾のスラッシュは不要
    :
設定値: resources/screenshots
結果: Vaultルート/resources/screenshots/
設定値: work/projects/project-a/assets
結果: Vaultルート/work/projects/project-a/assets/

サンプル表示:
設定画面の下部に保存先のプレビューが表示されます。

Images will be saved to: project/images/

画質設定

Image quality (WebP)(画像品質)

デフォルト値: 0.85
範囲: 0.1〜1.0(スライダーで調整、0.05刻み)
説明:

  • 1.0:最高品質(ファイルサイズが最大)
  • 0.85:推奨値(品質とサイズのバランス)
  • 0.5〜0.7:低品質(ファイルサイズ重視)
    設定方法:
    スライダーをドラッグして調整します。リアルタイムで値が表示されます。
    品質とファイルサイズの目安:
品質設定 ファイルサイズ 画質 推奨用途
1.0 100% 最高 写真の保管、印刷用
0.9 約80% 非常に高い 高品質が必要な画像
0.85 約70% 高い 一般的な用途(推奨)
0.8 約60% 高い スクリーンショット
0.7 約50% 中程度 図表、ダイアグラム
0.5 約30% 低い サムネイル、下書き
実例(1920×1080のスクリーンショット):
PNG形式: 約2.5MB
品質1.0: 約1.5MB(40%削減)
品質0.85: 約800KB(68%削減)← デフォルト
品質0.7: 約500KB(80%削減)

セキュリティ設定

Maximum image size(最大画像サイズ)

デフォルト値: 16777216(ピクセル)
説明:
画像の最大ピクセル数(幅 × 高さ)を制限します。
計算例:

  • 16,777,216ピクセル = 4096 × 4096
  • 4,194,304ピクセル = 2048 × 2048
  • 67,108,864ピクセル = 8192 × 8192
    設定方法:
    テキストフィールドに数値を入力します。
    推奨値:
用途 推奨値 サイズ例
一般的な用途 16,777,216 4096×4096
高セキュリティ環境 4,194,304 2048×2048
パワーユーザー 67,108,864 8192×8192
目的:
巨大な画像によるメモリ枯渇(DoS攻撃)を防ぎます。
エラー時の挙動:
制限を超える画像をペーストすると、エラーメッセージが表示され、画像は保存されません。
Image dimensions exceed maximum allowed size

Maximum file size (MB)(最大ファイルサイズ)

デフォルト値: 10 MB
説明:
ペースト可能な画像ファイルの最大サイズをMB単位で制限します。
設定方法:
テキストフィールドに数値を入力します(小数点も使用可能)。
推奨値:

用途 推奨値
一般的な用途 10 MB
高セキュリティ環境 5 MB
パワーユーザー 50 MB
:
設定値: 5
結果: 5MB以下の画像のみペースト可能
設定値: 2.5
結果: 2.5MB以下の画像のみペースト可能

エラー時の挙動:
制限を超える画像をペーストすると、エラーメッセージが表示されます。

Image file size exceeds 10MB limit

実践的な設定例

例1: 日記用の設定(デフォルト推奨)

Filename format: Timestamp
Timestamp format: YYYYMMDDHHmmss
Image folder location: Same folder as current note
Image folder name: attachments
Image quality: 0.85
Maximum image size: 16777216
Maximum file size: 10 MB

動作:

Vaultの構成:
├── Daily/
│   ├── 2024-11-30.md  ← 編集中
│   └── attachments/
│       └── 20241130143025.webp  ← 保存される

例2: プロジェクト管理用の設定

Filename format: Fixed name
Fixed filename: screenshot
Image folder location: Same folder as current note
Image folder name: images
Image quality: 0.8
Maximum image size: 16777216
Maximum file size: 10 MB

動作:

Vaultの構成:
├── Projects/
│   ├── ProjectA/
│   │   ├── overview.md  ← 編集中
│   │   └── images/
│   │       ├── screenshot.webp
│   │       ├── screenshot-1.webp
│   │       └── screenshot-2.webp

例3: 一元管理用の設定

Filename format: Timestamp
Timestamp format: YYYY-MM-DD_HHmmss
Image folder location: Vault root
Image folder name: assets
Image quality: 0.85
Maximum image size: 16777216
Maximum file size: 10 MB

動作:

Vaultの構成:
├── assets/
│   ├── 2024-11-30_143025.webp
│   ├── 2024-11-30_150312.webp
│   └── 2024-11-30_163540.webp
├── Projects/
│   └── overview.md  ← どこから貼っても assets/ に保存
└── Daily/
    └── 2024-11-30.md

例4: 高品質写真保存用

Filename format: Timestamp
Timestamp format: YYYY-MM-DD_HHmmss
Image folder location: Custom path
Custom folder path: media/photos
Image quality: 0.95
Maximum image size: 67108864
Maximum file size: 50 MB

用途: 写真をアーカイブとして保存する場合

トラブルシューティング

画像がペーストされない

確認事項:

  1. プラグインが有効化されているか
  2. クリップボードに画像データがあるか
  3. ファイルサイズや画像サイズが制限内か
    対処法:
  • コンソール(Ctrl + Shift + I)でエラーを確認
  • セキュリティ設定の制限を一時的に緩和して確認

フォルダが作成されない

原因:
親フォルダが存在しない場合、自動作成されません。
対処法:

  1. 手動で親フォルダを作成
  2. または、シンプルなフォルダ構造に変更
    :
設定: Custom folder path = "projects/work/screenshots"
親フォルダ "projects/work" が存在しない場合 → エラー
対処: 先に "projects/work" フォルダを手動で作成

同名ファイルが増え続ける

原因:
Fixed name形式で連番が増え続けている。
対処法:

  1. Timestamp形式に変更
  2. または、定期的に古い画像を整理

まとめ

「Paste Image as WebP」プラグインの設定は、用途に応じて柔軟にカスタマイズできます。

推奨設定:

  • 一般ユーザー:デフォルト設定のまま
  • こだわり派:Timestamp formatを読みやすい形式に
  • 大量の画像を扱う:品質を0.7〜0.8に下げてファイルサイズを削減
    自分の使い方に合わせて、最適な設定を見つけてください。

謝辞

ClaudeCode 様にはペアプロを嫌な顔せずしていただきました。
nanobanana pro 様には図解を作る際に大変お世話になりました。
いつもいつもありがとうございます。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?