Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Workers と Cloudflare Image Resizing で YouTube サムネイルを生成する

最終更新 Markdown で表示Agent セットアップ

このチュートリアルでは、Cloudflare Workers と Cloudflare Image Resizing を使い、カスタム YouTube サムネイルをプログラムで生成する方法を学びます。サムネイルのデザイン、コールトゥアクション、画像をカスタマイズして、より多くの視聴者に動画を見てもらうことができます。

このチュートリアルでは、ImagesImage ResizingCloudflare Workers の使い方を理解できます。

始める前に

すべてのチュートリアルは、Cloudflare Workers アカウント、C3、および Wrangler のセットアップが完了している前提です。セットアップは Get started ガイド で行います。

このチュートリアルを進めるには、マシンに Node、Cargo、Wrangler をインストールしてください。

学習目標

このチュートリアルでは、次のことを学びます。

  • Cloudflare ダッシュボードまたは API で、Cloudflare に画像をアップロードする。
  • Wrangler で Worker プロジェクトをセットアップする。
  • Worker 内の画像変換で画像を加工する。

画像をアップロードする

カスタムサムネイル画像を生成するには、まず背景画像を Cloudflare Images にアップロードします。この画像を変換して、サムネイルを生成します。

Cloudflare Images では、画像の保存、リサイズ、最適化、配信を、高速かつ安全に行えます。まず、Cloudflare ダッシュボードまたは Upload API で画像をアップロードします。

ダッシュボードでアップロードする

Cloudflare ダッシュボードで画像をアップロードするには、次の手順を実行します。

  1. Cloudflare ダッシュボードで、Transformations ページを開きます。

    Transformations を開く ↗
  2. Quick Upload で、画像をドラッグアンドドロップするか、クリックしてローカルファイルから選びます。

  3. アップロード後、生成された URL で画像を確認します。

API でアップロードする

URL からアップロード API で画像をアップロードするには、次の例を参照してください。

curl --request POST \
 --url https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
 --header 'Authorization: Bearer <API_TOKEN>' \
 --form 'url=<PATH_TO_IMAGE>' \
 --form 'metadata={"key":"value"}' \
 --form 'requireSignedURLs=false'
  • ACCOUNT_ID: アカウント設定で確認できる、現在のユーザーのアカウント ID です。
  • API_TOKEN: Images の権限をスコープして生成する必要があります。
  • PATH_TO_IMAGE: アップロードする画像の URL です。

次のような応答を受け取ります。

{
	"result": {
		"id": "2cdc28f0-017a-49c4-9ed7-87056c83901",
		"filename": "image.jpeg",
		"metadata": {
			"key": "value"
		},
		"uploaded": "2022-01-31T16:39:28.458Z",
		"requireSignedURLs": false,
		"variants": [
			"https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/public",
			"https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/thumbnail"
		]
	},
	"success": true,
	"errors": [],
	"messages": []
}

画像をアップロードしたので、動画サムネイルの背景画像として使います。

テキストを画像に変換する Worker を作成する

画像をアップロードしたあと、テキストを画像に変換する Worker を作成します。この画像を、アップロードした背景画像のオーバーレイとして使えます。rustwasm-worker-template を使います。

始める前に、次が必要です。

  • 新しいバージョンの Rust

  • cargo-generate サブコマンドへのアクセス:

    cargo install cargo-generate

worker-rust テンプレートで、新しい Worker プロジェクトを作成します。

cargo generate https://github.com/cloudflare/rustwasm-worker-template

プロジェクトディレクトリ内のファイルをいくつか変更します。

  1. lib.rs ファイルに、次のコードブロックを追加します。
use worker::*;
mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
   // Optionally, get more helpful error messages written to the console in the case of a panic.
   utils::set_panic_hook();

   let router = Router::new();
   router
       .get("/", |_, _| Response::ok("Hello from Workers!"))
       .run(req, env)
       .await
}
  1. worker-to-text プロジェクトディレクトリの Cargo.toml を更新し、テキストを PNG に描画する Rust パッケージ text-to-png を使います。次を実行して、依存関係として追加します。
cargo add [email protected]
  1. worker-to-text プロジェクトの lib.rs に、text_to_png ライブラリをインポートします。
use text_to_png::{TextPng, TextRenderer};
use worker::*;
mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
   // Optionally, get more helpful error messages written to the console in the case of a panic.
   utils::set_panic_hook();

   let router = Router::new();
   router
       .get("/", |_, _| Response::ok("Hello from Workers!"))
       .run(req, env)
       .await
}
  1. lib.rs を更新し、URL のクエリパラメーターとして渡されたテキストに基づいて画像変換を開始する handle-slash 関数を作成します。
use text_to_png::{TextPng, TextRenderer};
use worker::*;
mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
   // Optionally, get more helpful error messages written to the console in the case of a panic.
   utils::set_panic_hook();

   let router = Router::new();
   router
       .get("/", |_, _| Response::ok("Hello from Workers!"))
       .run(req, env)
       .await
}

async fn handle_slash(text: String) -> Result<Response> {}
  1. handle-slash 関数で、TextRenderer を renderer 値に割り当てて呼び出し、カスタムフォントを使うことを指定します。次に、render_text_to_png_data メソッドでテキストを画像形式に変換します。この例では、カスタムフォント(Inter-Bold.ttf)はプロジェクトルートの /assets フォルダーにあり、サムネイル生成に使います。この部分のコードは、自分のカスタムフォントファイルを指すように更新してください。
use text_to_png::{TextPng, TextRenderer};
use worker::*;
mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
   // Optionally, get more helpful error messages written to the console in the case of a panic.
   utils::set_panic_hook();

   let router = Router::new();
   router
       .get("/", |_, _| Response::ok("Hello from Workers!"))
       .run(req, env)
       .await
}

async fn handle_slash(text: String) -> Result<Response> {
  let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf"))
    .expect("Example font is definitely loadable");

  let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap();
}
  1. URL にクエリが渡されたときに handle_slash を呼び出すよう Router 関数を書き換えます。クエリがない場合は、応答として "Hello Worker!" を返します。
use text_to_png::{TextPng, TextRenderer};
use worker::*;
mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
   // Optionally, get more helpful error messages written to the console in the case of a panic.
   utils::set_panic_hook();

  let router = Router::new();
    router
      .get_async("/", |req, _| async move {
        if let Some(text) = req.url()?.query() {
          handle_slash(text.into()).await
        } else {
          handle_slash("Hello Worker!".into()).await
        }
      })
      .run(req, env)
        .await
}

async fn handle_slash(text: String) -> Result<Response> {
  let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf"))
    .expect("Example font is definitely loadable");

  let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap();
}
  1. lib.rs でヘッダーを content-type: image/png に設定し、応答が PNG 画像として正しく描画されるようにします。
use text_to_png::{TextPng, TextRenderer};
use worker::*;
mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
   // Optionally, get more helpful error messages written to the console in the case of a panic.
   utils::set_panic_hook();

   let router = Router::new();
    router
      .get_async("/", |req, _| async move {
        if let Some(text) = req.url()?.query() {
          handle_slash(text.into()).await
        } else {
          handle_slash("Hello Worker!".into()).await
        }
      })
      .run(req, env)
        .await
}

async fn handle_slash(text: String) -> Result<Response> {
  let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf"))
    .expect("Example font is definitely loadable");

  let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap();

  let mut headers = Headers::new();
  headers.set("content-type", "image/png")?;

  Ok(Response::from_bytes(text_png.data)?.with_headers(headers))
}

最終的な lib.rs は次のようになります。完全なコードの例は、GitHub のリポジトリにあります。

use text_to_png::{TextPng, TextRenderer};
use worker::*;

mod utils;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
    // Optionally, get more helpful error messages written to the console in the case of a panic.
    utils::set_panic_hook();

    let router = Router::new();

    router
        .get_async("/", |req, _| async move {
            if let Some(text) = req.url()?.query() {
                handle_slash(text.into()).await
            } else {
                handle_slash("Hello Worker!".into()).await
            }
        })
        .run(req, env)
        .await
}

async fn handle_slash(text: String) -> Result<Response> {
    let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("../assets/Inter-Bold.ttf"))
    .expect("Example font is definitely loadable");

    let text = if text.len() > 128 {
        "Nope".into()
    } else {
        text
    };

    let text = urlencoding::decode(&text).map_err(|_| worker::Error::BadEncoding)?;

    let text_png: TextPng = renderer.render_text_to_png_data(text.replace("+", " "), 60, "003682").unwrap();

    let mut headers = Headers::new();
    headers.set("content-type", "image/png")?;

    Ok(Response::from_bytes(text_png.data)?.with_headers(headers))
}

プロジェクトの更新が終わったら、次を実行して Worker 開発用のローカルサーバーを起動します。

npx wrangler dev

localhost インスタンスが起動し、画像が表示されます。

wrangler dev を実行して、Worker のローカルサーバーを起動します

カスタムテキストのクエリパラメーターを追加すると、次のようになります。

上記の手順に従うと、出力画像を受け取れます

Worker をデプロイするには、Wrangler ファイルを開き、name キーをプロジェクト名に更新します。このチュートリアルのプロジェクト名を使った例は次のとおりです。

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "worker-to-text"
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "worker-to-text"

次に npx wrangler deploy コマンドを実行して、Worker をデプロイします。

npx wrangler deploy

wrangler deploy を実行すると、Worker 用の .workers.dev ドメインが生成されます。このドメインを、メインのサムネイル画像で使います。

元の画像を表示する Worker を作成する

Images にアップロードした画像を配信する Worker を、次のコマンドで作成します。

npm create cloudflare@latest -- thumbnail-image

セットアップでは、次のオプションを選びます。

  • What would you like to start with? では、Hello World example を選びます。
  • Which template would you like to use? では、Worker only を選びます。
  • Which language do you want to use? では、JavaScript を選びます。
  • Do you want to use git for version control? では、Yes を選びます。
  • Do you want to deploy your application? では、No を選びます(デプロイ前にいくつか変更します)。

Worker の開発を始めるには、新しいプロジェクトディレクトリへ cd します。

cd thumbnail-image

thumbnail-image という名前の新しい Worker プロジェクトが作成されます。src/index.js ファイルに、次のコードブロックを追加します。

export default {
	async fetch(request, env) {
		const url = new URL(request.url);
		if (url.pathname === "/original-image") {
			const image = await fetch(
				`https://imagedelivery.net/${env.CLOUDFLARE_ACCOUNT_HASH}/${IMAGE_ID}/public`,
			);
			return image;
		}
		return new Response("Image Resizing with a Worker");
	},
};

env.CLOUDFLARE_ACCOUNT_HASHCloudflare アカウント ID に更新します。env.IMAGE_ID画像 ID に更新します。

Worker を実行し、/original-image ルートを開いて画像を確認します。

画像にカスタムテキストを追加する

Cloudflare の画像変換fetch メソッドと組み合わせて使い、動的なテキスト画像を背景画像の上にオーバーレイします。まず、結果の画像を別のルートで表示します。新しいルートを /thumbnail とします。

export default {
	async fetch(request, env) {
		const url = new URL(request.url);
		if (url.pathname === "/original-image") {
			const image = await fetch(
				`https://imagedelivery.net/${env.CLOUDFLARE_ACCOUNT_HASH}/${IMAGE_ID}/public`,
			);
			return image;
		}

		if (url.pathname === "/thumbnail") {
		}

		return new Response("Image Resizing with a Worker");
	},
};

次に、fetch メソッドで背景画像の上に画像変換を適用します。オーバーレイのオプションは options.cf.image にネストします。

export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		if (url.pathname === "/original-image") {
			const image = await fetch(
				`https://imagedelivery.net/${env.CLOUDFLARE_ACCOUNT_HASH}/${IMAGE_ID}/public`,
			);
			return image;
		}

		if (url.pathname === "/thumbnail") {
			fetch(imageURL, {
				cf: {
					image: {},
				},
			});
		}

		return new Response("Image Resizing with a Worker");
	},
};

imageURL は、背景画像として使う画像の URL です。cf.image オブジェクトで、背景画像に適用するオプションを指定します。

背景画像を GitHub の assets ディレクトリに追加し、変更を GitHub にプッシュします。画像を左クリックし、Copy Remote File Url を選んで、アップロードした画像の URL をコピーします。

imageURL の値を、コピーしたリモート URL に置き換えます。

if (url.pathname === "/thumbnail") {
	const imageURL =
		"https://github.com/lauragift21/social-image-demo/blob/1ed9044463b891561b7438ecdecbdd9da48cdb03/assets/cover.png?raw=true";
	fetch(imageURL, {
		cf: {
			image: {},
		},
	});
}

次に、image オブジェクトにオーバーレイオプションを追加します。画像を YouTube サムネイル向けの幅と高さにリサイズし、draw オプションで、デプロイ済みの text-to-image Worker の URL を使ってオーバーレイテキストを追加します。

fetch(imageURL, {
	cf: {
		image: {
			width: 1280,
			height: 720,
			draw: [
				{
					url: "https://text-to-image.examples.workers.dev",
					left: 40,
				},
			],
		},
	},
});

画像変換は、Worker をデプロイしたときだけテストできます。

Worker をデプロイするには、Wrangler ファイルを開き、name キーをプロジェクト名に更新します。このチュートリアルのプロジェクト名を使った例は次のとおりです。

{
	"$schema": "./node_modules/wrangler/config-schema.json",
	"name": "thumbnail-image"
}
"$schema" = "./node_modules/wrangler/config-schema.json"
name = "thumbnail-image"

次を実行して Worker をデプロイします。

npx wrangler deploy

このコマンドは、カスタムの workers.dev サブドメインに Worker をデプロイします。.workers.dev サブドメインを開き、/thumbnail ルートへ移動します。

リサイズされた画像と、テキスト Hello Workers! が表示されます。

上記の手順に従うと、リサイズした画像を生成できます。

次に、適用するテキストを動的にします。テキストを動的にすると、テキストを変えたときに画像へ自動で反映されます。

動的なテキストを追加するには、/thumbnail URL にクエリパラメーターで付いた任意のテキストを、パラメーターとして text-to-image Worker の URL に渡します。

for (const title of url.searchParams.values()) {
	try {
		const editedImage = await fetch(imageURL, {
			cf: {
				image: {
					width: 1280,
					height: 720,
					draw: [
						{
							url: `https://text-to-image.examples.workers.dev/?${title}`,
							left: 50,
						},
					],
				},
			},
		});
		return editedImage;
	} catch (error) {
		console.log(error);
	}
}

このチュートリアルを完了すると、カスタム YouTube サムネイルジェネレーターを作れます。

関連リソース

このチュートリアルでは、Cloudflare Workers と Cloudflare の画像変換を使い、カスタム YouTube サムネイルを生成する方法を学びました。Cloudflare Workers と画像変換について詳しくは、Worker で画像をリサイズする を参照してください。

役に立ちましたか?