0%
約5分 0文字

はじめに

このブログはClaudeに書かせました。Claudeと共に作製し、ここまでの内容をブログにまとめてください、と指示出ししました。

Obsidianでノート管理をしていると、カード表示機能を使って視覚的にノートを管理したくなりますよね。今回は、私のObsidianボルト内の593個すべてのノートに、NASA APOD(Astronomy Picture of the Day)の美しい天文写真をバナー画像として自動設定した方法をご紹介します。

NASA APOD Example ペルセウス座流星群 - NASA APOD より

背景:なぜNASA APODなのか?

課題

  • Obsidian Basesプラグインのカード表示機能でサムネイルを表示したい
  • 多くのノートでバナー画像が未設定(空欄)だった
  • Unsplash Source APIの廃止により、既存のUnsplash URLがすべてエラーに

解決策としてのNASA APOD

  • 毎日更新される高品質な天文写真
  • 1995年6月16日から現在まで、膨大なアーカイブ
  • 無料APIで簡単にアクセス可能
  • 科学的で美しい画像が豊富

実装手順

1. NASA API キーの取得

まず、NASA APIキーを取得します(無料):

  1. https://api.nasa.gov/ にアクセス
  2. “Generate API Key”をクリック
  3. メールアドレスを入力
  4. 送られてきたAPIキーを保存

レート制限

  • DEMO_KEY: 1時間30リクエストまで
  • 個人APIキー: 1時間1000リクエストまで(無料)

2. デイリーノート用テンプレートの作成

新規デイリーノート用のTemplaterテンプレートを作成:

<%*
// NASA APOD APIから今日の天文写真を取得
const apodUrl = 'https://api.nasa.gov/planetary/apod?api_key=YOUR_API_KEY';
const apodResponse = await fetch(apodUrl);

let bannerImage = '';
let imageTitle = '';

if (apodResponse.ok) {
    const apodData = await apodResponse.json();
    
    if (apodData.media_type === 'image') {
        bannerImage = apodData.hdurl || apodData.url;
        imageTitle = apodData.title;
    } else if (apodData.media_type === 'video') {
        bannerImage = apodData.thumbnail_url || 'image/Untitled 1.png';
        imageTitle = apodData.title + ' (Video)';
    }
} else {
    // フォールバック画像
    bannerImage = 'image/Untitled 1.png';
    imageTitle = 'Daily Note';
}

// frontmatterの生成
tR += `---
tags:
  - 日記
weather:
  - 晴れ
banner: "${bannerImage}"
banner_y: 0.5
banner_title: "${imageTitle}"
---`;
%>

3. 過去のノートを一括更新するPythonスクリプト

すべての既存ノートにNASA画像を適用するスクリプト:

#!/usr/bin/env python3
"""
すべてのObsidianノートにNASA APOD画像を適用
"""

import re
import time
import random
import requests
from pathlib import Path
from datetime import datetime, timedelta
from typing import Optional, Dict, Tuple
import hashlib

# NASA APOD API設定
NASA_APOD_URL = "https://api.nasa.gov/planetary/apod"
NASA_API_KEY = "YOUR_API_KEY_HERE"  # ここに実際のAPIキーを設定

# APODキャッシュ(APIコール削減用)
apod_cache = {}

def is_nasa_banner(banner_content: str) -> bool:
    """NASA APODのバナーかどうか判定"""
    nasa_patterns = [
        'apod.nasa.gov',
        'nasa.gov/apod',
        'astronomy picture of the day'
    ]
    banner_lower = banner_content.lower()
    return any(pattern in banner_lower for pattern in nasa_patterns)

def fetch_apod_for_date(date_str: str) -> Optional[Dict]:
    """特定の日付のAPOD画像を取得(キャッシュ付き)"""
    if date_str in apod_cache:
        return apod_cache[date_str]
    
    try:
        params = {"api_key": NASA_API_KEY, "date": date_str}
        response = requests.get(NASA_APOD_URL, params=params, timeout=10)
        
        if response.status_code == 200:
            data = response.json()
            if data.get("media_type") == "image":
                result = {
                    "url": data.get("hdurl") or data.get("url"),
                    "title": data.get("title", "")
                }
                apod_cache[date_str] = result
                return result
    except Exception:
        pass
    
    apod_cache[date_str] = None
    return None

def get_apod_for_file(file_path: Path) -> Optional[Dict]:
    """ファイルに応じたAPOD画像を取得"""
    # デイリーノートの場合は日付に対応するAPODを取得
    match = re.match(r'(\d{4})-(\d{2})-(\d{2})\.md$', file_path.name)
    if match:
        date_str = f"{match.group(1)}-{match.group(2)}-{match.group(3)}"
        apod_data = fetch_apod_for_date(date_str)
        if apod_data:
            return apod_data
    
    # それ以外のファイルは、ファイル名からハッシュを生成して
    # 一貫性のある日付を選択(同じファイルには同じ画像)
    file_hash = hashlib.md5(file_path.name.encode()).hexdigest()
    hash_number = int(file_hash[:8], 16)
    
    # 1995年6月16日からの日数を計算
    start_date = datetime(1995, 6, 16)
    end_date = datetime.now()
    days_range = (end_date - start_date).days
    
    # ハッシュ値を使って一貫性のある日付を選択
    days_offset = hash_number % days_range
    selected_date = start_date + timedelta(days=days_offset)
    date_str = selected_date.strftime("%Y-%m-%d")
    
    return fetch_apod_for_date(date_str)

def update_file(file_path: Path) -> Tuple[bool, str]:
    """ファイルのバナーをNASA画像に更新"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        if not content.startswith('---'):
            return False, "frontmatterなし"
        
        # NASA APOD画像を取得
        apod_data = get_apod_for_file(file_path)
        
        if apod_data:
            new_banner = f'banner: "{apod_data["url"]}"'
            
            # bannerフィールドの更新
            if re.search(r'^banner:\s*.*$', content, re.MULTILINE):
                content = re.sub(
                    r'^banner:\s*.*$',
                    new_banner,
                    content,
                    count=1,
                    flags=re.MULTILINE
                )
            else:
                # bannerフィールドを追加
                parts = content.split('---', 2)
                if len(parts) >= 3:
                    frontmatter = parts[1]
                    frontmatter = frontmatter.rstrip() + f'\n{new_banner}\n'
                    content = f'---{frontmatter}---{parts[2]}'
            
            # banner_y: 0.5を追加(縦横比の修正)
            if "banner_y:" not in content:
                content = re.sub(
                    r'^(banner:\s*[^\n]+)$',
                    r'\1\nbanner_y: 0.5',
                    content,
                    flags=re.MULTILINE
                )
            
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)
            
            return True, f"NASA: {apod_data['title'][:30]}"
        
        return False, "APOD取得失敗"
        
    except Exception as e:
        return False, f"エラー: {str(e)[:30]}"

4. 画像の縦横比問題の解決

Obsidianでバナー画像の縦横比がおかしくなる問題は、banner_y: 0.5を追加することで解決:

def add_banner_y(file_path: Path) -> bool:
    """banner_y: 0.5を追加して縦横比を修正"""
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()
    
    if "banner_y:" in content:
        return False
    
    # bannerフィールドの後にbanner_y: 0.5を追加
    pattern = r'^(banner:\s*[^\n]+)$'
    replacement = r'\1\nbanner_y: 0.5'
    
    new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
    
    if new_content != content:
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(new_content)
        return True
    
    return False

実行結果

📊 最終統計

  • 処理ファイル数: 593個
  • NASA APOD適用: 593個(100%)
    • デイリーノート: 275個(日付対応のAPOD)
    • その他のノート: 318個(ハッシュベースのAPOD)
  • 処理時間: 約5分

適用された画像の例

  • 🌌 Perseid Meteors from Durdle Door - ペルセウス座流星群
  • 🪐 Saturn’s Hexagon - 土星の六角形
  • 🌠 The Tarantula Nebula - タランチュラ星雲
  • 🌃 Milky Way over Uluru - ウルルの上の天の川
  • The Great Globular Cluster - 大球状星団

トラブルシューティング

1. Unsplash API廃止問題

2021年にUnsplash Source APIが廃止されたため、既存のUnsplash URLはすべてエラーに。以下のスクリプトで一括修正:

def fix_unsplash_urls():
    """UnsplashのURLをローカル画像に置換"""
    unsplash_pattern = r'banner:\s*"?(https://source\.unsplash\.com/[^"\n]+)"?'
    
    if re.search(unsplash_pattern, content):
        # ローカル画像に置換
        content = re.sub(unsplash_pattern, 
                         f'banner: {get_fallback_image()}', 
                         content)

2. APIレート制限対策

大量のファイルを処理する際のレート制限対策:

# キャッシュを使用してAPI呼び出しを削減
apod_cache = {}

# レート制限対策の遅延
time.sleep(0.3)  # 3リクエスト/秒程度

# エラー時の再試行
if response.status_code == 429:
    time.sleep(5)
    return None

メリット

  1. 視覚的な魅力: カード表示が格段に美しくなる
  2. 一貫性: すべてのノートに統一感のある高品質な画像
  3. 教育的価値: 毎日異なる天文写真で宇宙の知識も増える
  4. 自動化: 新規ノート作成時も自動でNASA画像を設定
  5. 無料: NASA APIは完全無料で利用可能

まとめ

ObsidianのノートにNASA APODを適用することで、ノート管理が視覚的にも楽しくなりました。特にカード表示での一覧性が向上し、ノートを開く楽しみも増えました。

APIキーの取得から実装まで、すべて無料で実現できるのも大きな魅力です。ぜひ皆さんのObsidianでも試してみてください!

関連リンク

最終更新: 2025年8月20日

ObsidianのすべてのノートにNASA天文写真を自動設定する方法 - 593個のファイルを美しく変身させた話

著者

semiramisu

公開日

2025 - 08 - 20

ライセンス CC BY-NC-SA 4.0

応援お待ちしています!

もしこの記事が役に立ったら、コーヒー1杯分の支援をいただけると嬉しいです。 いただいた支援は、より良いコンテンツ作成のために使わせていただきます。

PayPayで支援する

QRコード

(PayPayアプリで読み取ってください)

PayPay ID: @your-paypay-id

※ PayPayアプリの「送る」から
上記IDを検索してください

Buy Me a Coffeeで支援する

Buy Me A Coffee

クレジットカードやPayPalで支援できます

コメント