🔄

Pythonで作る!Obsidian形式のリンク(wikiリンク)を標準Markdownに一括変換するツール🔄

2024/12/30に公開

はじめに

こんにちは!今回は、ObsidianのMarkdownファイルを他のプラットフォーム用に変換するツールを作る方法をご紹介します。

「Obsidianで書いた記事をZennやQiitaに投稿したいけど、画像リンクやWikiリンクの形式が違うから手作業で直すのが大変...😫」

ほとんどの人はそんな経験はないと思いますが、私はまさにこの問題に直面したので、解決するためのツールをご紹介します!
※ちなみに、設定でwikiリンク化をオフにしていればこの問題は発生しません。

Obsidianと標準Markdownの違いを理解しよう

まず、なぜ変換が必要なのかを理解しましょう。

Obsidianの画像リンク形式:

![[cute_cat.png]]

標準的なMarkdownの画像リンク形式:

![cute_cat.png](cute_cat.png)

Obsidianのwikiリンク形式:

[[マイページ]]

標準的なMarkdownのリンク形式:

[マイページ](マイページ.md)

これらの違いを自動で変換するツールを作っていきます!

必要な準備物

このツールを作るために必要なものは以下の通りです。

  1. Python(バージョン3.6以上)

  2. 標準ライブラリ(追加でのインストール不要)

    • os: ファイルやフォルダを操作するためのライブラリ
    • re: 正規表現を使うためのライブラリ
    • tkinter: GUIを作るためのライブラリ

コードの詳しい解説

今回ご紹介するコードはこちらです。

import os
import re
import tkinter as tk
from tkinter import filedialog
import tkinter.messagebox as messagebox

def convert_links(folder_path):
    # 画像リンクのパターン(画像は.mdを付けない)
    image_pattern = r'!\[\[(.+?)\]\]'
    image_replacement = r'![\1](\1)'
    
    # 通常のWikiリンクのパターン(.mdを付ける)
    wiki_pattern = r'\[\[(.+?)\]\]'
    wiki_replacement = lambda m: f'[{m.group(1)}]({m.group(1)}.md)'
    
    for root, dirs, files in os.walk(folder_path):
        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                    
                    # 画像リンクを先に変換
                    new_content = re.sub(image_pattern, image_replacement, content)
                    # 通常のWikiリンクを変換(.mdを付ける)
                    new_content = re.sub(wiki_pattern, wiki_replacement, new_content)
                    
                    if new_content != content:
                        with open(file_path, 'w', encoding='utf-8') as f:
                            f.write(new_content)
                        print(f"変換完了: {file_path}")
                except Exception as e:
                    print(f"エラー発生 {file_path}: {str(e)}")

def select_folder():
    root = tk.Tk()
    root.withdraw()  # メインウィンドウを表示しない
    folder_path = filedialog.askdirectory(title="変換するマークダウンファイルを含むフォルダを選択してください")
    
    if folder_path:
        try:
            convert_links(folder_path)
            messagebox.showinfo("完了", "すべてのマークダウンファイルの変換が完了しました。")
        except Exception as e:
            messagebox.showerror("エラー", f"変換中にエラーが発生しました: {str(e)}")
    else:
        messagebox.showwarning("警告", "フォルダが選択されませんでした。")

if __name__ == "__main__":
    select_folder()

1. 必要なライブラリの読み込み

import os
import re
import tkinter as tk
from tkinter import filedialog
import tkinter.messagebox as messagebox

下3つは見慣れないかもしれませんが、このコードを使いやすくするGUIを作成するライブラリです。

  • os: フォルダ操作
  • re: テキストの置き換え
  • tkinter: 見やすい画面を作る

2. リンク変換の核心部分

def convert_links(folder_path):
    # 画像リンクのパターン(画像は.mdを付けない)
    image_pattern = r'!\[\[(.+?)\]\]'
    image_replacement = r'![\1](\1)'
    
    # 通常のWikiリンクのパターン(.mdを付ける)
    wiki_pattern = r'\[\[(.+?)\]\]'
    wiki_replacement = lambda m: f'[{m.group(1)}]({m.group(1)}.md)'

この部分では正規表現を使って、変換前と変換後の文字列を指定しています。

画像リンクの場合:

  • image_pattern = r'!\[\[(.+?)\]\]'

    • ![[:Obsidianの画像リンクの始まりを表す
    • (.+?):ファイル名を取り出す部分(例:cute_cat.png
    • \]\]:Obsidianの画像リンクの終わりを表す
  • image_replacement = r'![\1](\1)'

    • ![\1]:標準Markdownの画像タイトル部分
    • (\1):ファイルパス部分

Wikiリンクの場合:

  • wiki_pattern = r'\[\[(.+?)\]\]'

    • [[:Wikiリンクの始まり
    • (.+?):リンクテキストを取り出す部分
    • ]]:Wikiリンクの終わり
  • wiki_replacement

    • リンクテキストを取り出して
    • 標準的なMarkdownリンク形式に変換し
    • .md拡張子を追加

3. ファイルを探して変換する部分

    for root, dirs, files in os.walk(folder_path):
        for file in files:
            if file.endswith('.md'):
                try:

この部分は「フォルダの中を探索する」コードです。

  • フォルダの中を歩き回って(os.walk
  • .mdで終わるファイルを見つけたら
  • エラーが発生しても安全に処理できるようにtry文で囲んで
  • 変換作業を始めます

4. ファイルの中身を読んで書き換える

                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                    
                    # 画像リンクを先に変換
                    new_content = re.sub(image_pattern, image_replacement, content)
                    # 通常のWikiリンクを変換(.mdを付ける)
                    new_content = re.sub(wiki_pattern, wiki_replacement, new_content)

この部分は以下のような作業をしています。

  1. ファイルを開いて(open
  2. 中身を読んで(read
  3. まず画像リンクを変換
  4. 次にWikiリンクを変換

5. 変更を保存する

                    if new_content != content:
                        with open(file_path, 'w', encoding='utf-8') as f:
                            f.write(new_content)
                        print(f"変換完了: {file_path}")
                except Exception as e:
                    print(f"エラー発生 {file_path}: {str(e)}")

変換した内容に変更があれば、以下の処理を行います。

  1. ファイルを開いて('w'は書き込みモード)
  2. 新しい内容を保存(write
  3. エラーが発生した場合は詳細を表示

6. 使いやすい画面を作る

def select_folder():
    root = tk.Tk()
    root.withdraw()
    folder_path = filedialog.askdirectory(
        title="変換するマークダウンファイルを含むフォルダを選択してください"
    )

この部分で、フォルダを選びやすい画面を作っています。

  1. 画面の土台を作って(tk.Tk()
  2. 余計な窓を隠して(withdraw
  3. フォルダを選ぶ画面を表示(askdirectory

使い方の手順

それでは実際にこのコードを使ってみましょう。
今回はこのデモファイルを変換してみます。

wikiリンク変換デモ.md

# 日本の伝統文化
[[茶道]]は、日本を代表する伝統文化の一つです。[[千利休]]によって大成された茶道は、[[侘茶]]の精神を重視します。
![[sado-japan.jpg]]

このマークダウンファイルは「wikiリンク変換」というフォルダに保存してあり、

中身はこのようになっています。

ここにすべてのファイルが保存してあるので、正しくリンクが貼られていればきちんと表示されるはずですが、この文章をvscodeでプレビュー表示してみると、このようになります。

リンクが変換されずにそのままになっていますね。

コード実行

それではコードを実行していきます。
コードを実行するとフォルダ選択を求められるので、「wikiリンク変換」のフォルダを選択します。

変換が完了すると以下のポップアップが表示されます。

また、以下のような出力が出ていれば実行完了です。

変換完了: C:/your_directory/wikiリンク変換\wikiリンク変換デモ.md

それでは変換後のファイルを見てみましょう。

# 日本の伝統文化
[茶道](茶道.md)は、日本を代表する伝統文化の一つです。[千利休](千利休.md)によって大成された茶道は、[侘茶](侘茶.md)の精神を重視します。

![sado-japan.jpg](sado-japan.jpg)

このようにwikiリンクが変換されていれば成功です。

プレビューを見てみると、

このように画像がきれいに表示されています。
また、「茶道」「千利休」「侘茶」というリンクもクリックできるようになっており、例えば「茶道」をクリックすると、以下のように「茶道.md」を表示することができます。

よくある質問と回答

Q: 元のファイルは消えませんか?
A: 大丈夫です!内容を書き換えるだけで、ファイル自体は消えません。

Q: 日本語のファイル名でも動きますか?
A: はい!utf-8エンコーディングを使っているので、日本語もOKです。

Q: 間違って変換してしまった場合は?
A: 重要なファイルは事前にバックアップを取ることをおすすめします。

Q: エラーが発生した場合はどうなりますか?
A: エラーメッセージが表示され、問題のあるファイルがスキップされます。他のファイルの変換は続行されます。

発展的な使い方(上級者向け)

コードを少し改造することで、以下のような機能を追加できます:

  1. バックアップ機能
import shutil

def backup_file(file_path):
    backup_path = file_path + '.backup'
    shutil.copy2(file_path, backup_path)
  1. 変換前の確認機能
if messagebox.askyesno("確認", "変換を開始しますか?"):
    convert_links(folder_path)

最後に

このスクリプトが必要になる場面はなかなか無いと思いますが、いつか誰かのお役に立てれば幸いです。

Discussion