このチュートリアルでは、Cloudflare Workers と Cloudflare Image Resizing を使い、カスタム YouTube サムネイルをプログラムで生成する方法を学びます。サムネイルのデザイン、コールトゥアクション、画像をカスタマイズして、より多くの視聴者に動画を見てもらうことができます。
このチュートリアルでは、Images、Image Resizing、Cloudflare Workers の使い方を理解できます。
すべてのチュートリアルは、Cloudflare Workers アカウント、C3 ↗、および Wrangler のセットアップが完了している前提です。セットアップは Get started ガイド で行います。
このチュートリアルを進めるには、マシンに Node、Cargo、Wrangler をインストールしてください。
このチュートリアルでは、次のことを学びます。
- Cloudflare ダッシュボードまたは API で、Cloudflare に画像をアップロードする。
- Wrangler で Worker プロジェクトをセットアップする。
- Worker 内の画像変換で画像を加工する。
カスタムサムネイル画像を生成するには、まず背景画像を Cloudflare Images にアップロードします。この画像を変換して、サムネイルを生成します。
Cloudflare Images では、画像の保存、リサイズ、最適化、配信を、高速かつ安全に行えます。まず、Cloudflare ダッシュボードまたは Upload API で画像をアップロードします。
Cloudflare ダッシュボードで画像をアップロードするには、次の手順を実行します。
-
Cloudflare ダッシュボードで、Transformations ページを開きます。
Transformations を開く ↗ -
Quick Upload で、画像をドラッグアンドドロップするか、クリックしてローカルファイルから選びます。
-
アップロード後、生成された URL で画像を確認します。
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 を作成します。この画像を、アップロードした背景画像のオーバーレイとして使えます。rustwasm-worker-template ↗ を使います。
始める前に、次が必要です。
-
新しいバージョンの Rust ↗。
-
cargo-generateサブコマンドへのアクセス:cargo install cargo-generate
worker-rust テンプレートで、新しい Worker プロジェクトを作成します。
cargo generate https://github.com/cloudflare/rustwasm-worker-templateプロジェクトディレクトリ内のファイルをいくつか変更します。
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
}worker-to-textプロジェクトディレクトリのCargo.tomlを更新し、テキストを PNG に描画する Rust パッケージ text-to-png ↗ を使います。次を実行して、依存関係として追加します。
cargo add text-to-png@0.2.0worker-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
}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> {}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();
}- 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();
}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 devlocalhost インスタンスが起動し、画像が表示されます。
カスタムテキストのクエリパラメーターを追加すると、次のようになります。
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 deploywrangler deploy を実行すると、Worker 用の .workers.dev ドメインが生成されます。このドメインを、メインのサムネイル画像で使います。
Images にアップロードした画像を配信する Worker を、次のコマンドで作成します。
npm create cloudflare@latest -- thumbnail-imageyarn create cloudflare thumbnail-imagepnpm 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-imagethumbnail-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_HASH を Cloudflare アカウント 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 で画像をリサイズする を参照してください。