0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Wallabagに保存したYouTube動画のサムネイルを自動で設定する

0
Last updated at Posted at 2025-11-29

はじめに

Wallabagは素晴らしい「あとで読む」サービスですが、YouTube動画を保存したときにサムネイルが表示されないことがあります。

これはWallabagの記事取得エンジン(graby)がYouTubeの特殊な構造からサムネイルを安定して取得できないためです。特にYouTube Shortsでは顕著です。

本記事では、Dockerで動作する小さなワーカーを追加して、YouTube動画のサムネイルを自動設定する方法を紹介します。

仕組み

YouTubeのサムネイルは以下のURLで確実に取得できます:

https://img.youtube.com/vi/{VIDEO_ID}/maxresdefault.jpg

例えば https://www.youtube.com/watch?v=dQw4w9WgXcQ なら:

https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg

このワーカーは定期的にWallabagのDBをチェックし、サムネイルが未設定のYouTube記事を見つけたら自動でサムネイルURLを設定します。

成功例

image.png

好きな音楽をタブで保管していたためよく激重ブラウザ化していた( ・∇・)

前提条件

  • Docker + Docker Compose でWallabagを運用している
  • PostgreSQL または MySQL/MariaDB をDBに使用している

セットアップ

1. ディレクトリ作成

Wallabagのdocker-compose.ymlがあるディレクトリにytthumbフォルダを作成します。

mkdir ytthumb

2. Pythonスクリプト作成

PostgreSQLの場合

ytthumb/ytthumb.py:

#!/usr/bin/env python3
"""
YouTube Thumbnail Injector for Wallabag (PostgreSQL)
"""

import re
import os
import time
from urllib.parse import unquote
import psycopg2

YOUTUBE_RE = re.compile(
    r"(?:youtube\.com/(?:watch\?v(?:=|%3D)|shorts/)|youtu\.be/)([A-Za-z0-9_\-]{11})",
    re.IGNORECASE
)

def extract_video_id(url: str) -> str | None:
    decoded_url = unquote(url)
    match = YOUTUBE_RE.search(decoded_url)
    if match:
        return match.group(1)
    match = YOUTUBE_RE.search(url)
    return match.group(1) if match else None

def get_thumbnail_url(video_id: str) -> str:
    return f"https://img.youtube.com/vi/{video_id}/maxresdefault.jpg"

def get_db_connection():
    return psycopg2.connect(
        host=os.environ.get("DB_HOST", "wallabag-db"),
        port=os.environ.get("DB_PORT", "5432"),
        user=os.environ.get("DB_USER", "wallabag"),
        password=os.environ.get("DB_PASS", "wallabagpass"),
        database=os.environ.get("DB_NAME", "wallabag"),
    )

def update_thumbnails():
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT id, url FROM wallabag_entry
                WHERE (preview_picture IS NULL OR preview_picture = '')
                  AND (url LIKE '%youtube.com%' OR url LIKE '%youtu.be%')
            """)
            rows = cur.fetchall()

            for entry_id, url in rows:
                video_id = extract_video_id(url)
                if video_id:
                    thumb_url = get_thumbnail_url(video_id)
                    cur.execute(
                        "UPDATE wallabag_entry SET preview_picture = %s WHERE id = %s",
                        (thumb_url, entry_id)
                    )
                    print(f"[UPDATE] id={entry_id} -> {thumb_url}")

        conn.commit()
    finally:
        conn.close()

def main():
    interval = int(os.environ.get("CHECK_INTERVAL", "300"))
    print(f"YouTube Thumbnail Injector started (interval: {interval}s)")

    while True:
        try:
            update_thumbnails()
        except Exception as e:
            print(f"[ERROR] {e}")
        time.sleep(interval)

if __name__ == "__main__":
    main()

MySQL/MariaDBの場合

ytthumb/ytthumb.py:

#!/usr/bin/env python3
"""
YouTube Thumbnail Injector for Wallabag (MySQL/MariaDB)
"""

import re
import os
import time
from urllib.parse import unquote
import pymysql

YOUTUBE_RE = re.compile(
    r"(?:youtube\.com/(?:watch\?v(?:=|%3D)|shorts/)|youtu\.be/)([A-Za-z0-9_\-]{11})",
    re.IGNORECASE
)

def extract_video_id(url: str) -> str | None:
    decoded_url = unquote(url)
    match = YOUTUBE_RE.search(decoded_url)
    if match:
        return match.group(1)
    match = YOUTUBE_RE.search(url)
    return match.group(1) if match else None

def get_thumbnail_url(video_id: str) -> str:
    return f"https://img.youtube.com/vi/{video_id}/maxresdefault.jpg"

def get_db_connection():
    return pymysql.connect(
        host=os.environ.get("DB_HOST", "wallabag-db"),
        port=int(os.environ.get("DB_PORT", "3306")),
        user=os.environ.get("DB_USER", "wallabag"),
        password=os.environ.get("DB_PASS", "wallabagpass"),
        database=os.environ.get("DB_NAME", "wallabag"),
    )

def update_thumbnails():
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT id, url FROM wallabag_entry
                WHERE (preview_picture IS NULL OR preview_picture = '')
                  AND (url LIKE '%youtube.com%' OR url LIKE '%youtu.be%')
            """)
            rows = cur.fetchall()

            for entry_id, url in rows:
                video_id = extract_video_id(url)
                if video_id:
                    thumb_url = get_thumbnail_url(video_id)
                    cur.execute(
                        "UPDATE wallabag_entry SET preview_picture=%s WHERE id=%s",
                        (thumb_url, entry_id)
                    )
                    print(f"[UPDATE] id={entry_id} -> {thumb_url}")

        conn.commit()
    finally:
        conn.close()

def main():
    interval = int(os.environ.get("CHECK_INTERVAL", "300"))
    print(f"YouTube Thumbnail Injector started (interval: {interval}s)")

    while True:
        try:
            update_thumbnails()
        except Exception as e:
            print(f"[ERROR] {e}")
        time.sleep(interval)

if __name__ == "__main__":
    main()

3. requirements.txt作成

ytthumb/requirements.txt:

# PostgreSQLの場合
psycopg2-binary

# MySQL/MariaDBの場合は以下に変更
# pymysql

4. Dockerfile作成

ytthumb/Dockerfile:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY ytthumb.py .

CMD ["python", "-u", "ytthumb.py"]

5. docker-compose.ymlに追加

  wallabag-ytthumb:
    build: ./ytthumb
    container_name: wallabag-ytthumb
    depends_on:
      - wallabag-db  # あなたのDB名に合わせて変更
    environment:
      - DB_HOST=wallabag-db
      - DB_PORT=5432  # MySQLなら3306
      - DB_USER=wallabag
      - DB_PASS=your_password
      - DB_NAME=wallabag
      - CHECK_INTERVAL=300  # チェック間隔(秒)
      - TZ=Asia/Tokyo
    restart: unless-stopped

6. 起動

docker-compose up -d --build wallabag-ytthumb

7. 動作確認

docker logs wallabag-ytthumb

以下のようなログが出れば成功です:

YouTube Thumbnail Injector started (interval: 300s)
[UPDATE] id=16 -> https://img.youtube.com/vi/xxxxx/maxresdefault.jpg

対応URL形式

以下のYouTube URL形式に対応しています:

  • https://www.youtube.com/watch?v=VIDEO_ID
  • https://youtu.be/VIDEO_ID
  • https://www.youtube.com/shorts/VIDEO_ID
  • URLエンコードされた形式(v%3DVIDEO_ID

サムネイル解像度について

maxresdefault.jpgは最高解像度ですが、一部の動画では存在しない場合があります。その場合は以下に変更できます:

ファイル名 解像度
maxresdefault.jpg 1280x720
sddefault.jpg 640x480
hqdefault.jpg 480x360
mqdefault.jpg 320x180
default.jpg 120x90

確実性を重視するならhqdefault.jpgがおすすめです。

まとめ

  • Wallabag単体ではYouTubeのサムネイル取得が不安定
  • img.youtube.comを使えば確実にサムネイルを取得可能
  • 小さなDockerコンテナを追加するだけで自動化できる

これでWallabagのリスト表示がより見やすくなります。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?