コンテンツにスキップ
quickthumb
すべてのドキュメント

YouTubeサムネイル

1280×720のレイアウトを5種類紹介します。各画像は直下のスクリプトで描画したもので、リポジトリのexamples/にあります。

uvをインストールしてリポジトリをクローンし、ルートから実行してください。スクリプトはassets/から素材を読み込み、examples/へPNGを保存します。同名ファイルは上書きします。

git clone https://github.com/sjquant/quickthumb
cd quickthumb
uv run python examples/youtube_thumbnail_01.py

ビフォー・アフターのカード

「Better thumbnails. Faster.」の文字と、傾いたビフォー・アフターのカード2枚を並べたサムネイル

写真の背景に左側の見出し、右側の傾いたカード2枚を重ね、どんな変化が得られるかを具体的に見せます。

  • 横方向のLinearGradientをほぼ黒から透明へ変化させます。左側は文字を読める暗さを保ち、右側には写真を見せます。
  • 3行の見出しは1枚の.text()レイヤーです。各TextPartでサイズと色を変えています。
  • カードはStroke付きの回転したrectangleです。手前のカードにはShadowも付けます。文字もカードと同じrotationにします。
examples/youtube_thumbnail_01.py
"""
YouTube thumbnail: before / after cards.

- Photo background, darkened on the left by a horizontal gradient
- Three-line headline built from TextParts with different sizes and colors
- Two rotated cards (rectangle + stroke, plus a shadow on the front one)
"""

import os

from quickthumb import Canvas, Filter, LinearGradient, Shadow, Stroke, TextPart

FILE_DIR = os.path.dirname(__file__)
ASSETS_DIR = os.path.join(FILE_DIR, "..", "assets")

os.environ["QUICKTHUMB_FONT_DIR"] = os.path.join(ASSETS_DIR, "fonts")
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"

# Create 16:9 YouTube thumbnail (1280x720) with method chaining
(
    Canvas.from_aspect_ratio("16:9", 1280)
    .background(
        image=os.path.join(ASSETS_DIR, "images", "c-g-JgDUVGAXsso-unsplash.jpg"),
        effects=[Filter(brightness=0.76, contrast=1.08, saturation=0.5)],
    )
    # Darken the left side so the headline reads over the photo
    .background(
        gradient=LinearGradient(
            angle=90,
            stops=[("#000000FA", 0.0), ("#000000D6", 0.48), ("#00000014", 1.0)],
        )
    )
    .shape(
        shape="rectangle",
        position=("8%", "13%"),
        width=12,
        height=12,
        color="#0A84FF",
    )
    .text(
        content="BEFORE  /  AFTER",
        size=18,
        color="#D2D2D7",
        weight=700,
        letter_spacing=3,
        position=("10.5%", "12.5%"),
    )
    # Add headline and subtitle as rich text with different sizes and colors
    .text(
        content=[
            TextPart(text="BETTER\n", color="#F5F5F7", size=96),
            TextPart(text="THUMBNAILS.\n", color="#F5F5F7", size=78),
            TextPart(text="FASTER.", color="#0A84FF", size=96),
        ],
        size=96,
        line_height=0.98,
        position=("7%", "54%"),
        align=("left", "middle"),
        bold=True,
    )
    .shape(
        shape="rectangle",
        position=(832, 370),
        width=260,
        height=300,
        color="#242426",
        border_radius=16,
        rotation=-7,
        align=("center", "middle"),
        effects=[Stroke(width=2, color="#FFFFFF55")],
    )
    .text(
        content="BEFORE\nTOO MUCH\nNO FOCUS",
        size=22,
        color="#A1A1A6",
        weight=700,
        line_height=1.7,
        letter_spacing=1,
        position=(750, 270),
        rotation=-7,
    )
    .shape(
        shape="rectangle",
        position=(1054, 366),
        width=300,
        height=340,
        color="#0A84FF",
        border_radius=18,
        rotation=5,
        align=("center", "middle"),
        effects=[
            Stroke(width=2, color="#FFFFFFAA"),
            Shadow(offset_x=0, offset_y=18, color="#00000088", blur_radius=24),
        ],
    )
    .text(
        content="AFTER\nONE IDEA.\nONE FOCUS.",
        size=28,
        color="#FFFFFF",
        weight=800,
        line_height=1.55,
        position=(958, 248),
        rotation=5,
    )
    # Render the thumbnail
    .render(os.path.join(FILE_DIR, "youtube_thumbnail_01.png"))
)

print(f"✓ YouTube thumbnail created: {os.path.join(FILE_DIR, 'youtube_thumbnail_01.png')}")
print("  Replace the background and text content with your own.")

大きく問いかける

彩度を落とした写真に「Are you burning out?」と大きく書いたサムネイル

色をほとんど取り除いた写真に、問いかけを1つ大きく載せます。

  • Filter(saturation=0.32)で写真の色を抑え、オレンジのアクセントだけを強く残します。
  • 見出しの各行を独立したテキストレイヤーとしてピクセルで配置し、行間を正確に調整します。
  • 文言の上の細い線は、#FFFFFF33(不透明度20%の白)の高さ1pxの長方形です。
examples/youtube_thumbnail_02.py
"""
YouTube thumbnail: one large question.

- Desaturated photo background with an angled gradient for contrast
- Each headline line is its own text layer at a fixed position
- A 1px translucent rule above the hook line
"""

import os

from quickthumb import Canvas, Filter, FitMode, LinearGradient

FILE_DIR = os.path.dirname(__file__)
ASSETS_DIR = os.path.join(FILE_DIR, "..", "assets")

os.environ["QUICKTHUMB_FONT_DIR"] = os.path.join(ASSETS_DIR, "fonts")
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"

# Canvas dimensions
WIDTH = 1280
HEIGHT = 720

(
    Canvas(WIDTH, HEIGHT)
    # 1. Background Image with darkening
    .background(
        image=os.path.join(ASSETS_DIR, "images", "denise-jans-WIRvXd1PYlg-unsplash.jpg"),
        fit=FitMode.COVER,
        effects=[Filter(brightness=0.78, contrast=1.12, saturation=0.32)],
    )
    # 2. Gradient Overlay (Dark bottom-left to transparent top-right)
    # This ensures text readability on the left side
    .background(
        gradient=LinearGradient(
            angle=110,
            stops=[("#000000F7", 0.0), ("#000000B8", 0.44), ("#00000012", 1.0)],
        ),
    )
    # 3. Typography
    # Subtitle: "ARE YOU"
    .text(
        content="ARE YOU",
        size=20,
        color="#FF9F0A",
        position=(54, 74),
        weight=700,
        letter_spacing=3,
    )
    # Headline Line 1: "BURNING"
    .text(
        content="BURNING",
        size=148,
        color="#F5F5F7",
        position=(46, 108),
        weight=800,
    )
    # Headline Line 2: "OUT?"
    .text(
        content="OUT?",
        size=148,
        color="#FF9F0A",
        position=(46, 250),
        weight=800,
    )
    # 4. Footer / Hook
    .shape(
        shape="rectangle",
        position=(52, 554),
        width=620,
        height=1,
        color="#FFFFFF33",
    )
    .text(
        content="5 SIGNS YOU'RE IGNORING",
        size=32,
        color="#F5F5F7",
        position=(52, 592),
        weight=600,
    )
    # Render
    .render(os.path.join(FILE_DIR, "youtube_thumbnail_02.png"))
)

print(f"✓ Thumbnail created: {os.path.join(FILE_DIR, 'youtube_thumbnail_02.png')}")

人物を主役にする

青いパネル上の切り抜いた人物と「AI tools. What actually works.」の文字を配置したサムネイル

左に見出し、右に話し手を配置します。

  • URLから人物写真を取得し、remove_background=Trueで切り抜きます。rembg追加機能(Python 3.11以降)とネットワーク接続が必要です。uv run --extra rembg python examples/youtube_talking_head.pyで実行します。
  • position=(1280, 720)とalign=("right", "bottom")で、サイズによらず人物を右下に固定します。
  • max_width="55%"で人物にかかる前に見出しを折り返します。
examples/youtube_talking_head.py
"""
YouTube Talking-Head Thumbnail

Classic split layout with subject on the right and headline on the left:
- Black background with a blue panel on the right
- Portrait from a URL, cut out with remove_background=True (needs quickthumb[rembg])
  and pinned to the bottom-right corner
- Two-tone headline built from TextParts, wrapped with max_width
"""

import os

from quickthumb import Canvas, FitMode, LinearGradient, Shadow, Stroke, TextPart

FILE_DIR = os.path.dirname(__file__)
ASSETS_DIR = os.path.join(FILE_DIR, "..", "assets")
OUTPUT_PATH = os.path.join(FILE_DIR, "youtube_talking_head.png")

os.environ["QUICKTHUMB_FONT_DIR"] = os.path.join(ASSETS_DIR, "fonts")
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"

# Replace with your subject photo (local path or URL)
PORTRAIT_URL = (
    "https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&w=900&q=80"
)

(
    Canvas(1280, 720)
    .background(color="#000000")
    .background(
        gradient=LinearGradient(
            angle=90,
            stops=[("#000000", 0.0), ("#000000E8", 0.48), ("#00000011", 1.0)],
        )
    )
    .shape(
        shape="rectangle",
        position=(840, 0),
        width=440,
        height=720,
        color="#0A84FF",
    )
    .shape(
        shape="rectangle",
        position=(52, 132),
        width=56,
        height=5,
        color="#0A84FF",
        border_radius=3,
    )
    .text(
        content="30-DAY REVIEW",
        size=18,
        color="#A1A1A6",
        weight=900,
        letter_spacing=1,
        position=(52, 64),
    )
    # Main headline: stacked rich text with accent highlight
    .text(
        content=[
            TextPart(text="AI TOOLS.\n", color="#FFFFFF", weight=800),
            TextPart(text="WHAT ACTUALLY\nWORKS.", color="#0A84FF", weight=900),
        ],
        size=92,
        line_height=0.92,
        position=(48, 142),
        max_width="55%",
        effects=[
            Stroke(width=1, color="#000000"),
            Shadow(offset_x=0, offset_y=8, color="#00000099", blur_radius=14),
        ],
    )
    # Sub-copy anchored near the bottom
    .text(
        content="24 TESTED.  3 KEPT.",
        size=32,
        color="#D4D4D4",
        weight=500,
        position=(52, 620),
        effects=[Shadow(offset_x=2, offset_y=2, color="#000000", blur_radius=6)],
    )
    # Subject portrait — bottom-anchored on the right side
    .image(
        path=PORTRAIT_URL,
        position=(1280, 720),
        width=570,
        height=710,
        fit=FitMode.COVER,
        align=("right", "bottom"),
        remove_background=True,
        effects=[Shadow(offset_x=-14, offset_y=0, color="#00000088", blur_radius=22)],
    )
    .render(OUTPUT_PATH)
)

print(f"✓ Talking-head thumbnail created: {OUTPUT_PATH}")
print("  Replace PORTRAIT_URL with your subject photo for a personalised layout.")

リアクション

驚いた人物の切り抜きと「Viral, 12.4M views」を配置したサムネイル

大きな言葉、大きな数字、反応を見せる顔で構成します。

  • 人物の背後を中心にしたRadialGradientで淡い赤い光を加え、その上のLinearGradientで文字側を暗く保ちます。
  • 一番奥の#1は、不透明度13%、470pxのテキストレイヤーです。
  • 人物にはremove_background=True(rembg追加機能が必要)とFilter(saturation=0.08)を使い、ほぼ白黒にします。
examples/youtube_reaction.py
"""
YouTube Reaction / Commentary Thumbnail

- Dark base with a dim red radial glow behind the subject
- Cut-out portrait (remove_background=True, needs quickthumb[rembg]),
  almost fully desaturated
- A huge "#1" at low opacity behind everything
- "VIRAL" headline and a view-count line built from TextParts
"""

import os

from quickthumb import Canvas, Filter, FitMode, LinearGradient, RadialGradient, Shadow, TextPart

FILE_DIR = os.path.dirname(__file__)
ASSETS_DIR = os.path.join(FILE_DIR, "..", "assets")
OUTPUT_PATH = os.path.join(FILE_DIR, "youtube_reaction.png")

os.environ["QUICKTHUMB_FONT_DIR"] = os.path.join(ASSETS_DIR, "fonts")
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"

PORTRAIT_PATH = os.path.join(ASSETS_DIR, "images", "reaction-surprised.jpg")

(
    Canvas(1280, 720)
    .background(color="#000000")
    .background(
        gradient=RadialGradient(
            center=(0.82, 0.48),
            stops=[("#3D1110", 0.0), ("#000000", 0.68)],
        )
    )
    .background(
        gradient=LinearGradient(
            angle=90,
            stops=[("#000000", 0.0), ("#000000E8", 0.46), ("#00000011", 1.0)],
        )
    )
    .text(
        content="#1",
        size=470,
        color="#FF453A",
        opacity=0.13,
        weight=900,
        letter_spacing=-18,
        position=(1210, 28),
        align=("right", "top"),
    )
    .shape(
        shape="ellipse",
        position=(1034, 372),
        width=548,
        height=548,
        color="#1C1C1E",
        align=("center", "middle"),
    )
    .image(
        path=PORTRAIT_PATH,
        position=(1280, 720),
        width=620,
        height=710,
        fit=FitMode.COVER,
        align=("right", "bottom"),
        remove_background=True,
        effects=[
            Filter(contrast=1.08, saturation=0.08),
            Shadow(offset_x=-12, offset_y=12, color="#00000099", blur_radius=26),
        ],
    )
    .text(
        content="48H SIGNAL",
        size=17,
        color="#A1A1A6",
        weight=700,
        letter_spacing=3,
        position=(64, 54),
    )
    .shape(
        shape="rectangle",
        position=(64, 92),
        width=610,
        height=1,
        color="#FFFFFF33",
    )
    .text(
        content="VIRAL",
        size=212,
        color="#F5F5F7",
        weight=900,
        letter_spacing=-5,
        position=(52, 104),
    )
    .text(
        content=[
            TextPart(text="12.4M", color="#F5F5F7", weight=700),
            TextPart(text="  VIEWS", color="#A1A1A6", size=24, weight=600),
        ],
        size=92,
        color="#F5F5F7",
        weight=700,
        letter_spacing=-2,
        position=(60, 392),
    )
    .text(
        content="WHY IT WORKED — AND WHAT COMES NEXT",
        size=19,
        color="#A1A1A6",
        weight=600,
        letter_spacing=2,
        position=(64, 650),
    )
    .shape(
        shape="rectangle",
        position=(64, 620),
        width=610,
        height=1,
        color="#FFFFFF33",
    )
    .render(OUTPUT_PATH)
)

print(f"✓ Reaction / commentary thumbnail created: {OUTPUT_PATH}")

チュートリアル

短いコードを表示する青いパネルと「Master Python.」の文字を配置したサムネイル

写真を使わず、見出し、青いパネル、数行の「コード」で構成します。

  • パネルのコードは1枚のテキストレイヤーです。各TextPartで色と太さを変えるだけで、構文ハイライトのように見せられます。
  • 色はモジュールの定数(BLUE、WHITE、GRAY)なので、3行を変えるだけで配色を変更できます。
examples/youtube_tutorial_explainer.py
"""
YouTube Tutorial / Explainer Thumbnail

- Black background with a blue panel on the right; no photos
- Two-tone headline built from TextParts
- A code snippet on the panel, "highlighted" by giving each TextPart
  its own color and weight
"""

import os

from quickthumb import Canvas, TextPart

FILE_DIR = os.path.dirname(__file__)
ASSETS_DIR = os.path.join(FILE_DIR, "..", "assets")
OUTPUT_PATH = os.path.join(FILE_DIR, "youtube_tutorial_explainer.png")

os.environ["QUICKTHUMB_FONT_DIR"] = os.path.join(ASSETS_DIR, "fonts")
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"

BLUE = "#0A84FF"
WHITE = "#F5F5F7"
GRAY = "#86868B"


def main():
    """Render the tutorial thumbnail."""
    canvas = (
        Canvas(1280, 720)
        .background(color="#000000")
        .shape(
            shape="rectangle",
            position=(704, 0),
            width=576,
            height=720,
            color=BLUE,
        )
        .shape(
            shape="rectangle",
            position=(56, 58),
            width=10,
            height=10,
            color=BLUE,
        )
        .text(
            content="HOW TO  /  PYTHON",
            size=17,
            color="#A1A1A6",
            weight=700,
            letter_spacing=3,
            position=(88, 52),
        )
        .text(
            content="PYTHON  /  RUN",
            size=16,
            color="#FFFFFFB8",
            weight=700,
            letter_spacing=2,
            position=(1224, 52),
            align=("right", "top"),
        )
        .shape(
            shape="rectangle",
            position=(56, 96),
            width=1168,
            height=1,
            color="#FFFFFF33",
        )
        .text(
            content=[
                TextPart(text="MASTER\n", color=WHITE, weight=700),
                TextPart(text="PYTHON.", color=BLUE, weight=800),
            ],
            size=148,
            line_height=0.88,
            letter_spacing=-4,
            position=(50, 136),
        )
        .text(
            content="in 30 days — from scratch",
            size=27,
            color=GRAY,
            weight=500,
            position=(58, 474),
        )
        .text(
            content="BUILD  /  RUN  /  SHIP",
            size=15,
            color="#A1A1A6",
            weight=700,
            letter_spacing=3,
            position=(60, 646),
        )
        .text(
            content=[
                TextPart(text="def ", color="#FFFFFFB8", weight=500),
                TextPart(text="ship", color="#1D1D1F", weight=800),
                TextPart(text="(idea):\n", color="#FFFFFF", weight=500),
                TextPart(text="    build(idea)\n", color="#FFFFFF", weight=500),
                TextPart(text="    return ", color="#FFFFFFB8", weight=500),
                TextPart(text='"LIVE"', color="#1D1D1F", weight=800),
            ],
            size=42,
            line_height=1.55,
            position=(744, 168),
        )
        .shape(
            shape="rectangle",
            position=(744, 474),
            width=480,
            height=1,
            color="#FFFFFF66",
        )
        .text(
            content=">>>  SHIPPED / LIVE",
            size=36,
            color="#FFFFFF",
            weight=700,
            letter_spacing=1,
            position=(744, 510),
        )
    )

    canvas.render(OUTPUT_PATH)

    print(f"✓ Tutorial / explainer thumbnail created: {OUTPUT_PATH}")
    print("  Swap the step labels and headline text for any how-to topic.")


if __name__ == "__main__":
    main()