チュートリアル

ビデオのタイトル、トピック、ハッシュタグ自動生成の力を解き放とう

ミラン・キム

このチュートリアルでは、Twelve LabsのGist APIを使用して、インデックス化されたあらゆる動画のタイトル、トピック、ハッシュタグを自動的に生成するタイトル&ハッシュタグジェネレーターの構築手順を説明します。これにより、コンテンツクリエイターは、AIに手動で動画の内容を説明することなく、数秒で正確なメタデータを提案してもらうことができます。

このチュートリアルでは、Twelve LabsのGist APIを使用して、インデックス化されたあらゆる動画のタイトル、トピック、ハッシュタグを自動的に生成するタイトル&ハッシュタグジェネレーターの構築手順を説明します。これにより、コンテンツクリエイターは、AIに手動で動画の内容を説明することなく、数秒で正確なメタデータを提案してもらうことができます。

この記事の内容

No headings found on page

ニュースレターに登録する

ニュースレターに登録する

ビデオ理解に関する最新の技術進歩、チュートリアル、業界の動向をお届けします

ビデオ理解に関する最新の技術進歩、チュートリアル、業界の動向をお届けします

AIを活用してビデオを検索、分析、探索します。

2024/03/22

8分

記事へのリンクをコピー

前回のアプリケーションと記事に引き続き、今回はインフルエンサーがYouTubeなどのプラットフォームへの動画アップロードを効率的に準備できるように設計された、新しいツールを紹介します。動画制作を終えた後、インフルエンサーは魅力的なタイトルや関連するハッシュタグを考案するという課題にしばしば直面します。このアプリケーションは、Twelve Labs APIを活用してこのプロセスを効率化します。 

ユーザーはゼロから作成する代わりに、動画をアップロードまたはインデックス登録し、「Generate」をクリックするだけで、正確なタイトルの提案、トピックの推奨、ハッシュタグのアイデアを数秒で受け取ることができます。動画の内容をAIに説明する必要をなくすことで、このツールはインフルエンサーがカスタマイズされた提案に素早くアクセスできるようにし、アップロードのワークフローとエンゲージメントの可能性を高めます。

このアプリケーションを構築する手順を、ステップバイステップで詳しく見ていきましょう。

前提条件

  • Twelve LabsのAPIキーが必要です。お持ちでない場合は、Twelve Labs Playgroundにアクセスしてサインアップし、APIキーを生成してください。 

  • このアプリのすべてのファイルを含むリポジトリは、Githubで公開されています。

  • (あると望ましい)JavaScript、Node、React、およびReact Queryに関する基礎知識。これらに詳しくなくても心配はいりません。この記事における最大のポイントは、このアプリがどのようにTwelve Labs APIを使用しているかを確認することです!

アプリの構成について

__wf_reserved_inherit

このアプリは主に、GenerateTitlesAndHashtags、VideoFileUploadForm、Video、InputForm、Resultの5つのコンポーネントで構成されています。 

  • GenerateTitlesAndHashtags: 他のコンポーネントの親コンテナーとして機能します。子孫コンポーネントと共有される主要な状態(State)を保持します。

  • VideoUrlUploadForm: 動画ファイルを受け取り、TwelveLabs APIを使用して動画のインデックス登録を行うシンプルなフォームです。インデックス登録中の動画や、登録プロセスが完了するまでの進捗ステータスも表示します。 

  • Video: 指定されたURLによって動画を表示します。2つの異なるコンポーネント内で再利用されます。

  • InputForm: トピック、タイトル、ハッシュタグの3つのチェックボックスで構成されるフォームです。ユーザーは各フィールドのチェックを入れたり外したりできます。 

  • Result: TwelveLabs API(‘/gist’エンドポイント)を呼び出すことで、InputFormでチェックされたフィールドの結果を表示します。

また、このアプリにはAPI呼び出しを伴うすべてのコードを格納するserverと、状態、キャッシュ、およびデータフェッチを管理するためのカスタムReact Queryフック群であるapiHooks.jsが含まれています。

それでは、これらのコンポーネントがTwelve Labs APIとどのように連携して動作するのかを見ていきましょう。

Twelve Labs APIとアプリの連携の仕組み

1 - インデックス内の最新動画を表示する

__wf_reserved_inherit

このアプリは、あるインデックス内で直近にアップロードされた「1つの動画」のみを対象として動作します。そのため、初期ロード時には、デフォルトで指定されたインデックスの最新動画が表示されます。以下はその仕組みのプロセスです。 

  1. App.jsで指定されたインデックスのすべての動画を取得します(GET Videos

  2. レスポンスから最初の動画のIDを抽出し、GenerateTitlesAndHashtags.jsに渡します

  3. 動画IDを使用して動画の追記情報を取得し、動画のHLS(HTTP Live Streaming)URLを抽出してVideo.jsに渡します(GET Video

このように、動画を取得してページ上に最初の動画を表示するという一連の流れの中で、Twelve Labs APIに対して2つのGETリクエストを送信しています。それぞれのステップを詳しく見ていきましょう。

1.1 - App.jsで指定されたインデックスのすべての動画を取得する 

アプリの内部では、サーバーにリクエストを送信するReact QueryフックであるuseGetVideosを呼び出すことで動画が返されます。サーバーはその後、Twelve Labs APIにGETリクエストを送信し、インデックス内のすべての動画を取得します。(💡詳細はAPIドキュメントを参照してください - GET Videos

Server.js (line 53 - 73)

/** 動画を取得 */

app.get("/indexes/:indexId/videos", async (request, response, next) => {
 const params = {
   page_limit: request.query.page_limit,
 };


 try {
   const options = {
     method: "GET",
     url: `${API_BASE_URL}/indexes/${request.params.indexId}/videos`,
     headers: { ...HEADERS },
     data: { params },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message = error.response?.data?.message || "Error Getting Videos";
   return next({ status, message });
 }
});


返されるデータ(videos)は以下のようになります。

{
	"data": [
		{
			"_id": "64eadb7e37db8fa679cb47e2",
			"created_at": "2024-03-08T09:34:06Z",
			"metadata": {
				"duration": 213.72,
				"engine_ids": [
					"pegasus1",
				],
				"filename": "myFile.mp4",
				"fps": 25,
				"height": 720,
				"size": 25014061,
				"width": 1280
			}
		},


		{ 2つ目の動画 },
		{ 3つ目の動画 }, 
		
}

1.2 - レスポンスから最初の動画のIDを抽出し、SummarizeVideo.jsに渡す

返された動画データに基づいて、最初の動画のIDをGenerateTitlesAndHashtags.jsコンポーネントに渡します。 

App.js (line 34 - 38)

           <GenerateTitlesAndHashtags
             index={apiConfig.INDEX_ID}
             videoId={videos.data[0]?._id || null} // IDを渡す
             refetchVideos={refetchVideos}
           &sol;

1.3 - 動画IDを使用して動画の詳細情報を取得し、動画のソースURLを抽出してVideo.jsに渡す 

先ほど動画を取得した手順と同様に、動画の詳細情報を取得するため、サーバーにリクエストを送信するReact QueryフックであるuseGetVideoを使用しています。サーバーはその後、Twelve Labs APIにGETリクエストを送信し、特定の動画の詳細を取得します。(💡詳細はAPIドキュメントを参照してください - GET Video

Server.js (line 76 - 97)

/** インデックスの動画を取得 */

app.get(
 "/indexes/:indexId/videos/:videoId",
 async (request, response, next) => {
   const indexId = request.params.indexId;
   const videoId = request.params.videoId;


   try {
     const options = {
       method: "GET",
       url: `${API_BASE_URL}/indexes/${indexId}/videos/${videoId}`,
       headers: { ...HEADERS },
     };
     const apiResponse = await axios.request(options);
     response.json(apiResponse.data);
   } catch (error) {
     const status = error.response?.status || 500;
     const message = error.response?.data?.message || "Error Getting a Video";
     return next({ status, message });
   }
 }
);

これにより、必要となる動画のURLを含む詳細情報が返されます。GET Videosのリクエストからは動画のURLが取得できなかったことにお気づきかもしれません。それが、ここでGET Videoリクエストを実行している理由です。 

また、このアプリはローカルデバイスから動画ファイルをアップロードしてインデックス登録することのみを許可しているため、動画URLは「hls」オブジェクト内に格納されます。(前回のアプリではYouTubeのURLによるアップロードをサポートしていたため、YouTubeリンクは「source」オブジェクトに含まれていました)

{
	"_id": "64eadb7e37db8fa679cb47e2",
	"created_at": "2024-03-08T09:34:06Z",
	"indexed_at": "2024-03-08T09:39:14Z",
	"metadata": {
		"duration": 213.72,
		"engine_ids": [
			"pegasus1",
		],
		"filename": "myFile.mp4",
		"fps": 25,
		"height": 720,
		"size": 25014061,
		"video_title": "myFile.mp4",
		"width": 1280
	},
	"hls": {
		"video_url": "https://deuqpmn4rs7j5.cloudfront.net/…",
		"thumbnail_urls": [
			"https://deuqpmn4rs7j5.cloudfront.net/…"
		],
		"status": "COMPLETE",
		"updated_at": "2024-03-08T09:35:21.813Z"
	}
}

返された動画データに基づいてURLをVideoコンポーネントに渡し、React Playerを使用してそのURLから動画を表示します。

GenerateTitlesAndHashtags.js (line 105 - 111)

 {video && (
             <Video
               url={video.hls?.video_url} // URLを渡す
               width={"381px"}
               height={"214px"}

2 - 動画ファイルによる動画のアップロードとインデックス登録

__wf_reserved_inherit

このアプリでは、ユーザーはローカルデバイスから動画ファイルを選択してアップロードし、インデックス登録を行うことができます。このアプリケーションの主なユーザーは、公開準備はできているもののまだ非公開状態の動画を持っているインフルエンサーであることを想定しています。

動画のインデックス登録リクエスト(「タスク」と呼びます)を送信すると、インデックス登録タスクの進行状況を受け取ることができます。また、ユーザーが確認したり登録中に動画を視聴したりできるように、インデックス処理中も動画を画面上に表示するようにしました。動画のURLはインデックス処理の開始直後からは利用できず、インデックス処理の半ば頃から利用可能になる点にご注意ください。 

  1. VideoUrlUploadForm.jsで動画ファイルによる動画インデックス登録タスクを作成します(POST Task

  2. Task.jsでインデックス登録タスクの進行状況を受信して表示します(GET Task

それぞれのステップを1つずつ見ていきましょう。

2.1 - VideoUrlUploadForm.jsで動画ファイルによる動画インデックス登録タスクを作成する

ユーザーがローカルデバイスから動画ファイルを選択し、videoFileUploadFormを送信すると、動画のインデックス登録プロセスが始まります。

indexYouTubeVideoは、APIリクエストの送信に必要なデータ(言語、インデックスID、動画ファイルなど)をフォームとしてまとめ、サーバーにPOSTリクエストを送信します。その後、サーバーはTwelve Labs APIの「/tasks」エンドポイントにPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Task

server.js (line 119 - 158)

/** 分析用にYoutube動画をインデックス登録し、タスクIDを返す */

app.post(
 "/index",
 upload.single("video_file"),
 async (request, response, next) => {
   const formData = new FormData();


   // request.body からデータを追加
   Object.entries(request.body).forEach(([key, value]) => {
     formData.append(key, value);
   });


   const blob = new Blob([request.file.buffer], {
     type: request.file.mimetype,
   });


   formData.append("video_file", blob, request.file.originalname);


   const options = {
     method: "POST",
     url: `${API_BASE_URL}/tasks`,
     headers: {
       "x-api-key": TWELVE_LABS_API_KEY,
       accept: "application/json",
       "Content-Type":
         "multipart/form-data; boundary=---011000010111000001101001",
     },
     data: formData,
   };
   try {
     const apiResponse = await axios.request(options);
     response.json(apiResponse.data);
   } catch (error) {
     const status = error.response?.status || 500;
     const message =
       error.response?.data?.message || "Error indexing a YouTube Video";
     return next({ status, message });
   }
 }
);

作成されたばかりの動画タスクのIDが返されます。

{
	"_id": "65e9f732bb29f13bdd6f305a"
}

2.2 - Task.jsでインデックス登録タスクの進行状況を受け取って表示する

前ステップである「動画詳細の取得」から返されたタスクIDを使用して、タスクの詳細を取得し、ユーザーに対してタスクステータスを更新し続けます。

そのため、タスクIDが存在する時は、VideoFileUploadForm内にTaskコンポーネントをレンダリングするように設定しています。

VideoFileUploadForm.js (line 144 - 149)

{taskId && (
               <Task
                 taskId={taskId}
                 refetchVideos={refetchVideos}

Taskコンポーネント内では、サーバーにGETリクエストを送信するuseGetTask React Queryフックを使用してデータを受け取ります。その後、サーバーは以下のようにTwelve Labs APIにGETリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - GET Task

server.js (line 160 - 177)

/** 特定のインデックス作成タスクのステータスを確認 */
app.get("/tasks/:taskId", async (request, response, next) => {
 const taskId = request.params.taskId;


 try {
   const options = {
     method: "GET",
     url: `${API_BASE_URL}/tasks/${taskId}`,
     headers: { ...HEADERS },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message = error.response?.data?.message || "Error getting a task";
   return next({ status, message });
 }
});

以下のようなタスク詳細が返されます。

{
	"_id": "65e9f732bb29f13bdd6f305a",
	"index_id": "65e9f6d6bb29f13bdd6f3059",
	"video_id": "65e9f73248db9fa679cb47a2",
	"status": "indexing",
	"metadata": {
		"filename": "fileName.mp4",
		"duration": 327.326988,
		"width": 1920,
		"height": 1080
	},
	"created_at": "2024-03-07T17:19:46.277Z",
	"updated_at": "2024-03-07T17:27:27.135Z",
	"estimated_time": "2024-03-07T17:25:59.621Z",
	"type": "index_task_info",
	"hls": {
		"video_url": "...",
		"thumbnail_urls": [
			"..."
		],
		"status": "COMPLETE",
		"updated_at": "2024-03-07T17:21:23.921Z"
	}
}

「status」が「ready」になるまで、useGetTaskフックは5,000ミリ秒ごとにデータを取り込み直すため、ユーザーはリアルタイムでタスクの進捗状況を確認できます。以下で、私がuseQueryのrefetchIntervalプロパティをどのように活用したか確認してください。 

apiHooks.js (line 70 - 84)

export function useGetTask(taskId) {
 return useQuery({
   queryKey: [keys.TASK, taskId],
   queryFn: () =>
     apiConfig.SERVER.get(`${apiConfig.TASKS_URL}/${taskId}`).then(
       (res) => res.data
     ),
   refetchInterval: (data) => {
     return data?.status === "ready" || data?.status === "failed"
       ? false
       : 5000;
   },
   refetchIntervalInBackground: true,
 });
}

3 - ユーザー入力を受け取り、結果を生成・表示する

__wf_reserved_inherit

いよいよ中核となる最も面白い部分 — 動画のトピック、タイトル、ハッシュタグの生成です!ユーザーの入力を受け取り、Twelve Labs APIのgistエンドポイントを使用して、テキストによる動画のトピック、タイトル、ハッシュタグを生成します。 

  1. InputForm.jsのチェックボックスフォームからユーザー入力を受け取ります

  2. 各フィールドプロンプトへのユーザー入力に基づいて、Result.jsで「/gist」API呼び出しを行います(POST Titles, topics, or hashtags

  3. Result.jsで結果を表示します

それぞれのステップを詳しく見ていきましょう。

3.1 - InputForm.jsのチェックボックスフォームからユーザーの入力を受け取る

InputFormは、トピック、タイトル、ハッシュタグの3つのチェックボックスフィールドで構成されるシンプルなフォームです。ユーザーが各フィールドを有効または無効にするたびに、それぞれのフィールド名が、タイプの集合である「types」状態に登録または削除されます。次のステップでTwelve Labs APIの「/gist」エンドポイントにリクエストを送信する際、「types」プロパティが必要になるためです。 

GenerateTitlesAndHashtags.js内でfield1、field2、field3の名前を設定しています。

GenerateTitlesAndHashtags.js (line 28)

 const [field1, field2, field3] = ["topic", "title", "hashtag"];


InputForm.js (line 31 - 47) 
if (topicRef.current?.checked) {
     types.add(field1);
   } else {
     types.delete(field1);
   }


   if (titleRef.current?.checked) {
     types.add(field2);
   } else {
     types.delete(field2);
   }


   if (hashtagRef.current?.checked) {
     types.add(field3);
   } else {
     types.delete(field3);
   }

3.2 - 各フィールドプロンプトへのユーザー入力に基づいて、Result.jsで「/gist」API呼び出しを行う

フォームが送信され、動画IDと種類が設定されると、Result.jsからuseGenerateTitleTopicHashtagフックが呼び出されます。このフックはサーバーに対してリクエストを行い、そこからTwelve Labs APIへのAPIリクエストが実行されます(💡詳細はAPIドキュメントを参照してください - POST Titles, topics, or hashtags

参考までに、前回のアプリケーションでは要約、チャプター、ハイライトに対してそれぞれ3回別個にリクエストを行う必要がありましたが、ここでは1つのAPIリクエストだけでタイトル、トピック、およびハッシュタグをまとめて直接生成することができます。「/summary」エンドポイントの詳細についてはこちらをご覧ください。

server.js (line 98 - 117)

/** 動画の要約(Gist)を生成 */
app.post("/videos/:videoId/gist", async (request, response, next) => {
 const videoId = request.params.videoId;
 let types = request.body.data;
 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/gist`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...types, video_id: videoId },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message =
     error.response?.data?.message || "Error Generating Gist of a Video";
   return next({ status, message });
 }
});

レスポンスの「id」および、リクエストに含めたタイプ群からなるオブジェクトが返されます。例えば、リクエストに3つのタイプすべてを含めた場合、以下のようにトピック、タイトル、ハッシュタグの3つすべてを含んだ結果データを受け取ることができます。

{
	"id": "c7a420ef-ef2a-4a68-af65-3078af2d92fc",
	"title": "One-Pot Chicken and Rice: Cooking a Flavorful Dish from Marination to Serving",
	"topics": [
		" One-Pot Chicken and Rice Cooking Tutorial"
	],
	"hashtags": [
		"OnePotMeals",
		"ChickenRecipes",
		"RiceCooking",
		"MarinationTechniques",
		"CookingTutorials",
		"Spices",
		"YogurtMarinade"
	]
}

3.3 - Result.jsで結果を表示する

上記の手順で取得したレスポンスに基づき、Result コンポーネントの中で結果が表示されます。「topics」および「hashtags」は配列形式で出力され、「title」は文字列になります。

まとめ

Twelve Labsの「/gist」APIを使用すると、あらゆる動画のタイトル、トピック、ハッシュタグを簡単に生成できます。動画を作成したものの、YouTubeなどのプラットフォームに投稿する前に、魅力的かつ的確なタイトルやハッシュタグを考える必要がある場合に非常に強力な味方となります。このツールは、動画に特化した極めて実用的なタイトル/トピック/ハッシュタグ構築ツールとして活用できるでしょう。このアプリとチュートリアルが素晴らしいアイデアの提供に役立つことを願っています。いつものように、ハッピーコーディング!

次のステップ

前回のアプリケーションと記事に引き続き、今回はインフルエンサーがYouTubeなどのプラットフォームへの動画アップロードを効率的に準備できるように設計された、新しいツールを紹介します。動画制作を終えた後、インフルエンサーは魅力的なタイトルや関連するハッシュタグを考案するという課題にしばしば直面します。このアプリケーションは、Twelve Labs APIを活用してこのプロセスを効率化します。 

ユーザーはゼロから作成する代わりに、動画をアップロードまたはインデックス登録し、「Generate」をクリックするだけで、正確なタイトルの提案、トピックの推奨、ハッシュタグのアイデアを数秒で受け取ることができます。動画の内容をAIに説明する必要をなくすことで、このツールはインフルエンサーがカスタマイズされた提案に素早くアクセスできるようにし、アップロードのワークフローとエンゲージメントの可能性を高めます。

このアプリケーションを構築する手順を、ステップバイステップで詳しく見ていきましょう。

前提条件

  • Twelve LabsのAPIキーが必要です。お持ちでない場合は、Twelve Labs Playgroundにアクセスしてサインアップし、APIキーを生成してください。 

  • このアプリのすべてのファイルを含むリポジトリは、Githubで公開されています。

  • (あると望ましい)JavaScript、Node、React、およびReact Queryに関する基礎知識。これらに詳しくなくても心配はいりません。この記事における最大のポイントは、このアプリがどのようにTwelve Labs APIを使用しているかを確認することです!

アプリの構成について

__wf_reserved_inherit

このアプリは主に、GenerateTitlesAndHashtags、VideoFileUploadForm、Video、InputForm、Resultの5つのコンポーネントで構成されています。 

  • GenerateTitlesAndHashtags: 他のコンポーネントの親コンテナーとして機能します。子孫コンポーネントと共有される主要な状態(State)を保持します。

  • VideoUrlUploadForm: 動画ファイルを受け取り、TwelveLabs APIを使用して動画のインデックス登録を行うシンプルなフォームです。インデックス登録中の動画や、登録プロセスが完了するまでの進捗ステータスも表示します。 

  • Video: 指定されたURLによって動画を表示します。2つの異なるコンポーネント内で再利用されます。

  • InputForm: トピック、タイトル、ハッシュタグの3つのチェックボックスで構成されるフォームです。ユーザーは各フィールドのチェックを入れたり外したりできます。 

  • Result: TwelveLabs API(‘/gist’エンドポイント)を呼び出すことで、InputFormでチェックされたフィールドの結果を表示します。

また、このアプリにはAPI呼び出しを伴うすべてのコードを格納するserverと、状態、キャッシュ、およびデータフェッチを管理するためのカスタムReact Queryフック群であるapiHooks.jsが含まれています。

それでは、これらのコンポーネントがTwelve Labs APIとどのように連携して動作するのかを見ていきましょう。

Twelve Labs APIとアプリの連携の仕組み

1 - インデックス内の最新動画を表示する

__wf_reserved_inherit

このアプリは、あるインデックス内で直近にアップロードされた「1つの動画」のみを対象として動作します。そのため、初期ロード時には、デフォルトで指定されたインデックスの最新動画が表示されます。以下はその仕組みのプロセスです。 

  1. App.jsで指定されたインデックスのすべての動画を取得します(GET Videos

  2. レスポンスから最初の動画のIDを抽出し、GenerateTitlesAndHashtags.jsに渡します

  3. 動画IDを使用して動画の追記情報を取得し、動画のHLS(HTTP Live Streaming)URLを抽出してVideo.jsに渡します(GET Video

このように、動画を取得してページ上に最初の動画を表示するという一連の流れの中で、Twelve Labs APIに対して2つのGETリクエストを送信しています。それぞれのステップを詳しく見ていきましょう。

1.1 - App.jsで指定されたインデックスのすべての動画を取得する 

アプリの内部では、サーバーにリクエストを送信するReact QueryフックであるuseGetVideosを呼び出すことで動画が返されます。サーバーはその後、Twelve Labs APIにGETリクエストを送信し、インデックス内のすべての動画を取得します。(💡詳細はAPIドキュメントを参照してください - GET Videos

Server.js (line 53 - 73)

/** 動画を取得 */

app.get("/indexes/:indexId/videos", async (request, response, next) => {
 const params = {
   page_limit: request.query.page_limit,
 };


 try {
   const options = {
     method: "GET",
     url: `${API_BASE_URL}/indexes/${request.params.indexId}/videos`,
     headers: { ...HEADERS },
     data: { params },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message = error.response?.data?.message || "Error Getting Videos";
   return next({ status, message });
 }
});


返されるデータ(videos)は以下のようになります。

{
	"data": [
		{
			"_id": "64eadb7e37db8fa679cb47e2",
			"created_at": "2024-03-08T09:34:06Z",
			"metadata": {
				"duration": 213.72,
				"engine_ids": [
					"pegasus1",
				],
				"filename": "myFile.mp4",
				"fps": 25,
				"height": 720,
				"size": 25014061,
				"width": 1280
			}
		},


		{ 2つ目の動画 },
		{ 3つ目の動画 }, 
		
}

1.2 - レスポンスから最初の動画のIDを抽出し、SummarizeVideo.jsに渡す

返された動画データに基づいて、最初の動画のIDをGenerateTitlesAndHashtags.jsコンポーネントに渡します。 

App.js (line 34 - 38)

           <GenerateTitlesAndHashtags
             index={apiConfig.INDEX_ID}
             videoId={videos.data[0]?._id || null} // IDを渡す
             refetchVideos={refetchVideos}
           &sol;

1.3 - 動画IDを使用して動画の詳細情報を取得し、動画のソースURLを抽出してVideo.jsに渡す 

先ほど動画を取得した手順と同様に、動画の詳細情報を取得するため、サーバーにリクエストを送信するReact QueryフックであるuseGetVideoを使用しています。サーバーはその後、Twelve Labs APIにGETリクエストを送信し、特定の動画の詳細を取得します。(💡詳細はAPIドキュメントを参照してください - GET Video

Server.js (line 76 - 97)

/** インデックスの動画を取得 */

app.get(
 "/indexes/:indexId/videos/:videoId",
 async (request, response, next) => {
   const indexId = request.params.indexId;
   const videoId = request.params.videoId;


   try {
     const options = {
       method: "GET",
       url: `${API_BASE_URL}/indexes/${indexId}/videos/${videoId}`,
       headers: { ...HEADERS },
     };
     const apiResponse = await axios.request(options);
     response.json(apiResponse.data);
   } catch (error) {
     const status = error.response?.status || 500;
     const message = error.response?.data?.message || "Error Getting a Video";
     return next({ status, message });
   }
 }
);

これにより、必要となる動画のURLを含む詳細情報が返されます。GET Videosのリクエストからは動画のURLが取得できなかったことにお気づきかもしれません。それが、ここでGET Videoリクエストを実行している理由です。 

また、このアプリはローカルデバイスから動画ファイルをアップロードしてインデックス登録することのみを許可しているため、動画URLは「hls」オブジェクト内に格納されます。(前回のアプリではYouTubeのURLによるアップロードをサポートしていたため、YouTubeリンクは「source」オブジェクトに含まれていました)

{
	"_id": "64eadb7e37db8fa679cb47e2",
	"created_at": "2024-03-08T09:34:06Z",
	"indexed_at": "2024-03-08T09:39:14Z",
	"metadata": {
		"duration": 213.72,
		"engine_ids": [
			"pegasus1",
		],
		"filename": "myFile.mp4",
		"fps": 25,
		"height": 720,
		"size": 25014061,
		"video_title": "myFile.mp4",
		"width": 1280
	},
	"hls": {
		"video_url": "https://deuqpmn4rs7j5.cloudfront.net/…",
		"thumbnail_urls": [
			"https://deuqpmn4rs7j5.cloudfront.net/…"
		],
		"status": "COMPLETE",
		"updated_at": "2024-03-08T09:35:21.813Z"
	}
}

返された動画データに基づいてURLをVideoコンポーネントに渡し、React Playerを使用してそのURLから動画を表示します。

GenerateTitlesAndHashtags.js (line 105 - 111)

 {video && (
             <Video
               url={video.hls?.video_url} // URLを渡す
               width={"381px"}
               height={"214px"}

2 - 動画ファイルによる動画のアップロードとインデックス登録

__wf_reserved_inherit

このアプリでは、ユーザーはローカルデバイスから動画ファイルを選択してアップロードし、インデックス登録を行うことができます。このアプリケーションの主なユーザーは、公開準備はできているもののまだ非公開状態の動画を持っているインフルエンサーであることを想定しています。

動画のインデックス登録リクエスト(「タスク」と呼びます)を送信すると、インデックス登録タスクの進行状況を受け取ることができます。また、ユーザーが確認したり登録中に動画を視聴したりできるように、インデックス処理中も動画を画面上に表示するようにしました。動画のURLはインデックス処理の開始直後からは利用できず、インデックス処理の半ば頃から利用可能になる点にご注意ください。 

  1. VideoUrlUploadForm.jsで動画ファイルによる動画インデックス登録タスクを作成します(POST Task

  2. Task.jsでインデックス登録タスクの進行状況を受信して表示します(GET Task

それぞれのステップを1つずつ見ていきましょう。

2.1 - VideoUrlUploadForm.jsで動画ファイルによる動画インデックス登録タスクを作成する

ユーザーがローカルデバイスから動画ファイルを選択し、videoFileUploadFormを送信すると、動画のインデックス登録プロセスが始まります。

indexYouTubeVideoは、APIリクエストの送信に必要なデータ(言語、インデックスID、動画ファイルなど)をフォームとしてまとめ、サーバーにPOSTリクエストを送信します。その後、サーバーはTwelve Labs APIの「/tasks」エンドポイントにPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Task

server.js (line 119 - 158)

/** 分析用にYoutube動画をインデックス登録し、タスクIDを返す */

app.post(
 "/index",
 upload.single("video_file"),
 async (request, response, next) => {
   const formData = new FormData();


   // request.body からデータを追加
   Object.entries(request.body).forEach(([key, value]) => {
     formData.append(key, value);
   });


   const blob = new Blob([request.file.buffer], {
     type: request.file.mimetype,
   });


   formData.append("video_file", blob, request.file.originalname);


   const options = {
     method: "POST",
     url: `${API_BASE_URL}/tasks`,
     headers: {
       "x-api-key": TWELVE_LABS_API_KEY,
       accept: "application/json",
       "Content-Type":
         "multipart/form-data; boundary=---011000010111000001101001",
     },
     data: formData,
   };
   try {
     const apiResponse = await axios.request(options);
     response.json(apiResponse.data);
   } catch (error) {
     const status = error.response?.status || 500;
     const message =
       error.response?.data?.message || "Error indexing a YouTube Video";
     return next({ status, message });
   }
 }
);

作成されたばかりの動画タスクのIDが返されます。

{
	"_id": "65e9f732bb29f13bdd6f305a"
}

2.2 - Task.jsでインデックス登録タスクの進行状況を受け取って表示する

前ステップである「動画詳細の取得」から返されたタスクIDを使用して、タスクの詳細を取得し、ユーザーに対してタスクステータスを更新し続けます。

そのため、タスクIDが存在する時は、VideoFileUploadForm内にTaskコンポーネントをレンダリングするように設定しています。

VideoFileUploadForm.js (line 144 - 149)

{taskId && (
               <Task
                 taskId={taskId}
                 refetchVideos={refetchVideos}

Taskコンポーネント内では、サーバーにGETリクエストを送信するuseGetTask React Queryフックを使用してデータを受け取ります。その後、サーバーは以下のようにTwelve Labs APIにGETリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - GET Task

server.js (line 160 - 177)

/** 特定のインデックス作成タスクのステータスを確認 */
app.get("/tasks/:taskId", async (request, response, next) => {
 const taskId = request.params.taskId;


 try {
   const options = {
     method: "GET",
     url: `${API_BASE_URL}/tasks/${taskId}`,
     headers: { ...HEADERS },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message = error.response?.data?.message || "Error getting a task";
   return next({ status, message });
 }
});

以下のようなタスク詳細が返されます。

{
	"_id": "65e9f732bb29f13bdd6f305a",
	"index_id": "65e9f6d6bb29f13bdd6f3059",
	"video_id": "65e9f73248db9fa679cb47a2",
	"status": "indexing",
	"metadata": {
		"filename": "fileName.mp4",
		"duration": 327.326988,
		"width": 1920,
		"height": 1080
	},
	"created_at": "2024-03-07T17:19:46.277Z",
	"updated_at": "2024-03-07T17:27:27.135Z",
	"estimated_time": "2024-03-07T17:25:59.621Z",
	"type": "index_task_info",
	"hls": {
		"video_url": "...",
		"thumbnail_urls": [
			"..."
		],
		"status": "COMPLETE",
		"updated_at": "2024-03-07T17:21:23.921Z"
	}
}

「status」が「ready」になるまで、useGetTaskフックは5,000ミリ秒ごとにデータを取り込み直すため、ユーザーはリアルタイムでタスクの進捗状況を確認できます。以下で、私がuseQueryのrefetchIntervalプロパティをどのように活用したか確認してください。 

apiHooks.js (line 70 - 84)

export function useGetTask(taskId) {
 return useQuery({
   queryKey: [keys.TASK, taskId],
   queryFn: () =>
     apiConfig.SERVER.get(`${apiConfig.TASKS_URL}/${taskId}`).then(
       (res) => res.data
     ),
   refetchInterval: (data) => {
     return data?.status === "ready" || data?.status === "failed"
       ? false
       : 5000;
   },
   refetchIntervalInBackground: true,
 });
}

3 - ユーザー入力を受け取り、結果を生成・表示する

__wf_reserved_inherit

いよいよ中核となる最も面白い部分 — 動画のトピック、タイトル、ハッシュタグの生成です!ユーザーの入力を受け取り、Twelve Labs APIのgistエンドポイントを使用して、テキストによる動画のトピック、タイトル、ハッシュタグを生成します。 

  1. InputForm.jsのチェックボックスフォームからユーザー入力を受け取ります

  2. 各フィールドプロンプトへのユーザー入力に基づいて、Result.jsで「/gist」API呼び出しを行います(POST Titles, topics, or hashtags

  3. Result.jsで結果を表示します

それぞれのステップを詳しく見ていきましょう。

3.1 - InputForm.jsのチェックボックスフォームからユーザーの入力を受け取る

InputFormは、トピック、タイトル、ハッシュタグの3つのチェックボックスフィールドで構成されるシンプルなフォームです。ユーザーが各フィールドを有効または無効にするたびに、それぞれのフィールド名が、タイプの集合である「types」状態に登録または削除されます。次のステップでTwelve Labs APIの「/gist」エンドポイントにリクエストを送信する際、「types」プロパティが必要になるためです。 

GenerateTitlesAndHashtags.js内でfield1、field2、field3の名前を設定しています。

GenerateTitlesAndHashtags.js (line 28)

 const [field1, field2, field3] = ["topic", "title", "hashtag"];


InputForm.js (line 31 - 47) 
if (topicRef.current?.checked) {
     types.add(field1);
   } else {
     types.delete(field1);
   }


   if (titleRef.current?.checked) {
     types.add(field2);
   } else {
     types.delete(field2);
   }


   if (hashtagRef.current?.checked) {
     types.add(field3);
   } else {
     types.delete(field3);
   }

3.2 - 各フィールドプロンプトへのユーザー入力に基づいて、Result.jsで「/gist」API呼び出しを行う

フォームが送信され、動画IDと種類が設定されると、Result.jsからuseGenerateTitleTopicHashtagフックが呼び出されます。このフックはサーバーに対してリクエストを行い、そこからTwelve Labs APIへのAPIリクエストが実行されます(💡詳細はAPIドキュメントを参照してください - POST Titles, topics, or hashtags

参考までに、前回のアプリケーションでは要約、チャプター、ハイライトに対してそれぞれ3回別個にリクエストを行う必要がありましたが、ここでは1つのAPIリクエストだけでタイトル、トピック、およびハッシュタグをまとめて直接生成することができます。「/summary」エンドポイントの詳細についてはこちらをご覧ください。

server.js (line 98 - 117)

/** 動画の要約(Gist)を生成 */
app.post("/videos/:videoId/gist", async (request, response, next) => {
 const videoId = request.params.videoId;
 let types = request.body.data;
 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/gist`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...types, video_id: videoId },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message =
     error.response?.data?.message || "Error Generating Gist of a Video";
   return next({ status, message });
 }
});

レスポンスの「id」および、リクエストに含めたタイプ群からなるオブジェクトが返されます。例えば、リクエストに3つのタイプすべてを含めた場合、以下のようにトピック、タイトル、ハッシュタグの3つすべてを含んだ結果データを受け取ることができます。

{
	"id": "c7a420ef-ef2a-4a68-af65-3078af2d92fc",
	"title": "One-Pot Chicken and Rice: Cooking a Flavorful Dish from Marination to Serving",
	"topics": [
		" One-Pot Chicken and Rice Cooking Tutorial"
	],
	"hashtags": [
		"OnePotMeals",
		"ChickenRecipes",
		"RiceCooking",
		"MarinationTechniques",
		"CookingTutorials",
		"Spices",
		"YogurtMarinade"
	]
}

3.3 - Result.jsで結果を表示する

上記の手順で取得したレスポンスに基づき、Result コンポーネントの中で結果が表示されます。「topics」および「hashtags」は配列形式で出力され、「title」は文字列になります。

まとめ

Twelve Labsの「/gist」APIを使用すると、あらゆる動画のタイトル、トピック、ハッシュタグを簡単に生成できます。動画を作成したものの、YouTubeなどのプラットフォームに投稿する前に、魅力的かつ的確なタイトルやハッシュタグを考える必要がある場合に非常に強力な味方となります。このツールは、動画に特化した極めて実用的なタイトル/トピック/ハッシュタグ構築ツールとして活用できるでしょう。このアプリとチュートリアルが素晴らしいアイデアの提供に役立つことを願っています。いつものように、ハッピーコーディング!

次のステップ

前回のアプリケーションと記事に引き続き、今回はインフルエンサーがYouTubeなどのプラットフォームへの動画アップロードを効率的に準備できるように設計された、新しいツールを紹介します。動画制作を終えた後、インフルエンサーは魅力的なタイトルや関連するハッシュタグを考案するという課題にしばしば直面します。このアプリケーションは、Twelve Labs APIを活用してこのプロセスを効率化します。 

ユーザーはゼロから作成する代わりに、動画をアップロードまたはインデックス登録し、「Generate」をクリックするだけで、正確なタイトルの提案、トピックの推奨、ハッシュタグのアイデアを数秒で受け取ることができます。動画の内容をAIに説明する必要をなくすことで、このツールはインフルエンサーがカスタマイズされた提案に素早くアクセスできるようにし、アップロードのワークフローとエンゲージメントの可能性を高めます。

このアプリケーションを構築する手順を、ステップバイステップで詳しく見ていきましょう。

前提条件

  • Twelve LabsのAPIキーが必要です。お持ちでない場合は、Twelve Labs Playgroundにアクセスしてサインアップし、APIキーを生成してください。 

  • このアプリのすべてのファイルを含むリポジトリは、Githubで公開されています。

  • (あると望ましい)JavaScript、Node、React、およびReact Queryに関する基礎知識。これらに詳しくなくても心配はいりません。この記事における最大のポイントは、このアプリがどのようにTwelve Labs APIを使用しているかを確認することです!

アプリの構成について

__wf_reserved_inherit

このアプリは主に、GenerateTitlesAndHashtags、VideoFileUploadForm、Video、InputForm、Resultの5つのコンポーネントで構成されています。 

  • GenerateTitlesAndHashtags: 他のコンポーネントの親コンテナーとして機能します。子孫コンポーネントと共有される主要な状態(State)を保持します。

  • VideoUrlUploadForm: 動画ファイルを受け取り、TwelveLabs APIを使用して動画のインデックス登録を行うシンプルなフォームです。インデックス登録中の動画や、登録プロセスが完了するまでの進捗ステータスも表示します。 

  • Video: 指定されたURLによって動画を表示します。2つの異なるコンポーネント内で再利用されます。

  • InputForm: トピック、タイトル、ハッシュタグの3つのチェックボックスで構成されるフォームです。ユーザーは各フィールドのチェックを入れたり外したりできます。 

  • Result: TwelveLabs API(‘/gist’エンドポイント)を呼び出すことで、InputFormでチェックされたフィールドの結果を表示します。

また、このアプリにはAPI呼び出しを伴うすべてのコードを格納するserverと、状態、キャッシュ、およびデータフェッチを管理するためのカスタムReact Queryフック群であるapiHooks.jsが含まれています。

それでは、これらのコンポーネントがTwelve Labs APIとどのように連携して動作するのかを見ていきましょう。

Twelve Labs APIとアプリの連携の仕組み

1 - インデックス内の最新動画を表示する

__wf_reserved_inherit

このアプリは、あるインデックス内で直近にアップロードされた「1つの動画」のみを対象として動作します。そのため、初期ロード時には、デフォルトで指定されたインデックスの最新動画が表示されます。以下はその仕組みのプロセスです。 

  1. App.jsで指定されたインデックスのすべての動画を取得します(GET Videos

  2. レスポンスから最初の動画のIDを抽出し、GenerateTitlesAndHashtags.jsに渡します

  3. 動画IDを使用して動画の追記情報を取得し、動画のHLS(HTTP Live Streaming)URLを抽出してVideo.jsに渡します(GET Video

このように、動画を取得してページ上に最初の動画を表示するという一連の流れの中で、Twelve Labs APIに対して2つのGETリクエストを送信しています。それぞれのステップを詳しく見ていきましょう。

1.1 - App.jsで指定されたインデックスのすべての動画を取得する 

アプリの内部では、サーバーにリクエストを送信するReact QueryフックであるuseGetVideosを呼び出すことで動画が返されます。サーバーはその後、Twelve Labs APIにGETリクエストを送信し、インデックス内のすべての動画を取得します。(💡詳細はAPIドキュメントを参照してください - GET Videos

Server.js (line 53 - 73)

/** 動画を取得 */

app.get("/indexes/:indexId/videos", async (request, response, next) => {
 const params = {
   page_limit: request.query.page_limit,
 };


 try {
   const options = {
     method: "GET",
     url: `${API_BASE_URL}/indexes/${request.params.indexId}/videos`,
     headers: { ...HEADERS },
     data: { params },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message = error.response?.data?.message || "Error Getting Videos";
   return next({ status, message });
 }
});


返されるデータ(videos)は以下のようになります。

{
	"data": [
		{
			"_id": "64eadb7e37db8fa679cb47e2",
			"created_at": "2024-03-08T09:34:06Z",
			"metadata": {
				"duration": 213.72,
				"engine_ids": [
					"pegasus1",
				],
				"filename": "myFile.mp4",
				"fps": 25,
				"height": 720,
				"size": 25014061,
				"width": 1280
			}
		},


		{ 2つ目の動画 },
		{ 3つ目の動画 }, 
		
}

1.2 - レスポンスから最初の動画のIDを抽出し、SummarizeVideo.jsに渡す

返された動画データに基づいて、最初の動画のIDをGenerateTitlesAndHashtags.jsコンポーネントに渡します。 

App.js (line 34 - 38)

           <GenerateTitlesAndHashtags
             index={apiConfig.INDEX_ID}
             videoId={videos.data[0]?._id || null} // IDを渡す
             refetchVideos={refetchVideos}
           &sol;

1.3 - 動画IDを使用して動画の詳細情報を取得し、動画のソースURLを抽出してVideo.jsに渡す 

先ほど動画を取得した手順と同様に、動画の詳細情報を取得するため、サーバーにリクエストを送信するReact QueryフックであるuseGetVideoを使用しています。サーバーはその後、Twelve Labs APIにGETリクエストを送信し、特定の動画の詳細を取得します。(💡詳細はAPIドキュメントを参照してください - GET Video

Server.js (line 76 - 97)

/** インデックスの動画を取得 */

app.get(
 "/indexes/:indexId/videos/:videoId",
 async (request, response, next) => {
   const indexId = request.params.indexId;
   const videoId = request.params.videoId;


   try {
     const options = {
       method: "GET",
       url: `${API_BASE_URL}/indexes/${indexId}/videos/${videoId}`,
       headers: { ...HEADERS },
     };
     const apiResponse = await axios.request(options);
     response.json(apiResponse.data);
   } catch (error) {
     const status = error.response?.status || 500;
     const message = error.response?.data?.message || "Error Getting a Video";
     return next({ status, message });
   }
 }
);

これにより、必要となる動画のURLを含む詳細情報が返されます。GET Videosのリクエストからは動画のURLが取得できなかったことにお気づきかもしれません。それが、ここでGET Videoリクエストを実行している理由です。 

また、このアプリはローカルデバイスから動画ファイルをアップロードしてインデックス登録することのみを許可しているため、動画URLは「hls」オブジェクト内に格納されます。(前回のアプリではYouTubeのURLによるアップロードをサポートしていたため、YouTubeリンクは「source」オブジェクトに含まれていました)

{
	"_id": "64eadb7e37db8fa679cb47e2",
	"created_at": "2024-03-08T09:34:06Z",
	"indexed_at": "2024-03-08T09:39:14Z",
	"metadata": {
		"duration": 213.72,
		"engine_ids": [
			"pegasus1",
		],
		"filename": "myFile.mp4",
		"fps": 25,
		"height": 720,
		"size": 25014061,
		"video_title": "myFile.mp4",
		"width": 1280
	},
	"hls": {
		"video_url": "https://deuqpmn4rs7j5.cloudfront.net/…",
		"thumbnail_urls": [
			"https://deuqpmn4rs7j5.cloudfront.net/…"
		],
		"status": "COMPLETE",
		"updated_at": "2024-03-08T09:35:21.813Z"
	}
}

返された動画データに基づいてURLをVideoコンポーネントに渡し、React Playerを使用してそのURLから動画を表示します。

GenerateTitlesAndHashtags.js (line 105 - 111)

 {video && (
             <Video
               url={video.hls?.video_url} // URLを渡す
               width={"381px"}
               height={"214px"}

2 - 動画ファイルによる動画のアップロードとインデックス登録

__wf_reserved_inherit

このアプリでは、ユーザーはローカルデバイスから動画ファイルを選択してアップロードし、インデックス登録を行うことができます。このアプリケーションの主なユーザーは、公開準備はできているもののまだ非公開状態の動画を持っているインフルエンサーであることを想定しています。

動画のインデックス登録リクエスト(「タスク」と呼びます)を送信すると、インデックス登録タスクの進行状況を受け取ることができます。また、ユーザーが確認したり登録中に動画を視聴したりできるように、インデックス処理中も動画を画面上に表示するようにしました。動画のURLはインデックス処理の開始直後からは利用できず、インデックス処理の半ば頃から利用可能になる点にご注意ください。 

  1. VideoUrlUploadForm.jsで動画ファイルによる動画インデックス登録タスクを作成します(POST Task

  2. Task.jsでインデックス登録タスクの進行状況を受信して表示します(GET Task

それぞれのステップを1つずつ見ていきましょう。

2.1 - VideoUrlUploadForm.jsで動画ファイルによる動画インデックス登録タスクを作成する

ユーザーがローカルデバイスから動画ファイルを選択し、videoFileUploadFormを送信すると、動画のインデックス登録プロセスが始まります。

indexYouTubeVideoは、APIリクエストの送信に必要なデータ(言語、インデックスID、動画ファイルなど)をフォームとしてまとめ、サーバーにPOSTリクエストを送信します。その後、サーバーはTwelve Labs APIの「/tasks」エンドポイントにPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Task

server.js (line 119 - 158)

/** 分析用にYoutube動画をインデックス登録し、タスクIDを返す */

app.post(
 "/index",
 upload.single("video_file"),
 async (request, response, next) => {
   const formData = new FormData();


   // request.body からデータを追加
   Object.entries(request.body).forEach(([key, value]) => {
     formData.append(key, value);
   });


   const blob = new Blob([request.file.buffer], {
     type: request.file.mimetype,
   });


   formData.append("video_file", blob, request.file.originalname);


   const options = {
     method: "POST",
     url: `${API_BASE_URL}/tasks`,
     headers: {
       "x-api-key": TWELVE_LABS_API_KEY,
       accept: "application/json",
       "Content-Type":
         "multipart/form-data; boundary=---011000010111000001101001",
     },
     data: formData,
   };
   try {
     const apiResponse = await axios.request(options);
     response.json(apiResponse.data);
   } catch (error) {
     const status = error.response?.status || 500;
     const message =
       error.response?.data?.message || "Error indexing a YouTube Video";
     return next({ status, message });
   }
 }
);

作成されたばかりの動画タスクのIDが返されます。

{
	"_id": "65e9f732bb29f13bdd6f305a"
}

2.2 - Task.jsでインデックス登録タスクの進行状況を受け取って表示する

前ステップである「動画詳細の取得」から返されたタスクIDを使用して、タスクの詳細を取得し、ユーザーに対してタスクステータスを更新し続けます。

そのため、タスクIDが存在する時は、VideoFileUploadForm内にTaskコンポーネントをレンダリングするように設定しています。

VideoFileUploadForm.js (line 144 - 149)

{taskId && (
               <Task
                 taskId={taskId}
                 refetchVideos={refetchVideos}

Taskコンポーネント内では、サーバーにGETリクエストを送信するuseGetTask React Queryフックを使用してデータを受け取ります。その後、サーバーは以下のようにTwelve Labs APIにGETリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - GET Task

server.js (line 160 - 177)

/** 特定のインデックス作成タスクのステータスを確認 */
app.get("/tasks/:taskId", async (request, response, next) => {
 const taskId = request.params.taskId;


 try {
   const options = {
     method: "GET",
     url: `${API_BASE_URL}/tasks/${taskId}`,
     headers: { ...HEADERS },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message = error.response?.data?.message || "Error getting a task";
   return next({ status, message });
 }
});

以下のようなタスク詳細が返されます。

{
	"_id": "65e9f732bb29f13bdd6f305a",
	"index_id": "65e9f6d6bb29f13bdd6f3059",
	"video_id": "65e9f73248db9fa679cb47a2",
	"status": "indexing",
	"metadata": {
		"filename": "fileName.mp4",
		"duration": 327.326988,
		"width": 1920,
		"height": 1080
	},
	"created_at": "2024-03-07T17:19:46.277Z",
	"updated_at": "2024-03-07T17:27:27.135Z",
	"estimated_time": "2024-03-07T17:25:59.621Z",
	"type": "index_task_info",
	"hls": {
		"video_url": "...",
		"thumbnail_urls": [
			"..."
		],
		"status": "COMPLETE",
		"updated_at": "2024-03-07T17:21:23.921Z"
	}
}

「status」が「ready」になるまで、useGetTaskフックは5,000ミリ秒ごとにデータを取り込み直すため、ユーザーはリアルタイムでタスクの進捗状況を確認できます。以下で、私がuseQueryのrefetchIntervalプロパティをどのように活用したか確認してください。 

apiHooks.js (line 70 - 84)

export function useGetTask(taskId) {
 return useQuery({
   queryKey: [keys.TASK, taskId],
   queryFn: () =>
     apiConfig.SERVER.get(`${apiConfig.TASKS_URL}/${taskId}`).then(
       (res) => res.data
     ),
   refetchInterval: (data) => {
     return data?.status === "ready" || data?.status === "failed"
       ? false
       : 5000;
   },
   refetchIntervalInBackground: true,
 });
}

3 - ユーザー入力を受け取り、結果を生成・表示する

__wf_reserved_inherit

いよいよ中核となる最も面白い部分 — 動画のトピック、タイトル、ハッシュタグの生成です!ユーザーの入力を受け取り、Twelve Labs APIのgistエンドポイントを使用して、テキストによる動画のトピック、タイトル、ハッシュタグを生成します。 

  1. InputForm.jsのチェックボックスフォームからユーザー入力を受け取ります

  2. 各フィールドプロンプトへのユーザー入力に基づいて、Result.jsで「/gist」API呼び出しを行います(POST Titles, topics, or hashtags

  3. Result.jsで結果を表示します

それぞれのステップを詳しく見ていきましょう。

3.1 - InputForm.jsのチェックボックスフォームからユーザーの入力を受け取る

InputFormは、トピック、タイトル、ハッシュタグの3つのチェックボックスフィールドで構成されるシンプルなフォームです。ユーザーが各フィールドを有効または無効にするたびに、それぞれのフィールド名が、タイプの集合である「types」状態に登録または削除されます。次のステップでTwelve Labs APIの「/gist」エンドポイントにリクエストを送信する際、「types」プロパティが必要になるためです。 

GenerateTitlesAndHashtags.js内でfield1、field2、field3の名前を設定しています。

GenerateTitlesAndHashtags.js (line 28)

 const [field1, field2, field3] = ["topic", "title", "hashtag"];


InputForm.js (line 31 - 47) 
if (topicRef.current?.checked) {
     types.add(field1);
   } else {
     types.delete(field1);
   }


   if (titleRef.current?.checked) {
     types.add(field2);
   } else {
     types.delete(field2);
   }


   if (hashtagRef.current?.checked) {
     types.add(field3);
   } else {
     types.delete(field3);
   }

3.2 - 各フィールドプロンプトへのユーザー入力に基づいて、Result.jsで「/gist」API呼び出しを行う

フォームが送信され、動画IDと種類が設定されると、Result.jsからuseGenerateTitleTopicHashtagフックが呼び出されます。このフックはサーバーに対してリクエストを行い、そこからTwelve Labs APIへのAPIリクエストが実行されます(💡詳細はAPIドキュメントを参照してください - POST Titles, topics, or hashtags

参考までに、前回のアプリケーションでは要約、チャプター、ハイライトに対してそれぞれ3回別個にリクエストを行う必要がありましたが、ここでは1つのAPIリクエストだけでタイトル、トピック、およびハッシュタグをまとめて直接生成することができます。「/summary」エンドポイントの詳細についてはこちらをご覧ください。

server.js (line 98 - 117)

/** 動画の要約(Gist)を生成 */
app.post("/videos/:videoId/gist", async (request, response, next) => {
 const videoId = request.params.videoId;
 let types = request.body.data;
 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/gist`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...types, video_id: videoId },
   };
   const apiResponse = await axios.request(options);
   response.json(apiResponse.data);
 } catch (error) {
   const status = error.response?.status || 500;
   const message =
     error.response?.data?.message || "Error Generating Gist of a Video";
   return next({ status, message });
 }
});

レスポンスの「id」および、リクエストに含めたタイプ群からなるオブジェクトが返されます。例えば、リクエストに3つのタイプすべてを含めた場合、以下のようにトピック、タイトル、ハッシュタグの3つすべてを含んだ結果データを受け取ることができます。

{
	"id": "c7a420ef-ef2a-4a68-af65-3078af2d92fc",
	"title": "One-Pot Chicken and Rice: Cooking a Flavorful Dish from Marination to Serving",
	"topics": [
		" One-Pot Chicken and Rice Cooking Tutorial"
	],
	"hashtags": [
		"OnePotMeals",
		"ChickenRecipes",
		"RiceCooking",
		"MarinationTechniques",
		"CookingTutorials",
		"Spices",
		"YogurtMarinade"
	]
}

3.3 - Result.jsで結果を表示する

上記の手順で取得したレスポンスに基づき、Result コンポーネントの中で結果が表示されます。「topics」および「hashtags」は配列形式で出力され、「title」は文字列になります。

まとめ

Twelve Labsの「/gist」APIを使用すると、あらゆる動画のタイトル、トピック、ハッシュタグを簡単に生成できます。動画を作成したものの、YouTubeなどのプラットフォームに投稿する前に、魅力的かつ的確なタイトルやハッシュタグを考える必要がある場合に非常に強力な味方となります。このツールは、動画に特化した極めて実用的なタイトル/トピック/ハッシュタグ構築ツールとして活用できるでしょう。このアプリとチュートリアルが素晴らしいアイデアの提供に役立つことを願っています。いつものように、ハッピーコーディング!

次のステップ