チュートリアル

YouTube動画の書き起こし要約を自動で取得する方法とは?

ミラン・キム

このチュートリアルでは、Twelve Labs Generate APIを使用して、インデックス登録された任意の動画の要約、チャプター、ハイライトを自動的に作成する「YouTube動画の要約」アプリの構築手順を説明します。フロントエンドにはReactを使用し、出力タイプの選択やタイムスタンプ付きの結果表示を行います。

このチュートリアルでは、Twelve Labs Generate APIを使用して、インデックス登録された任意の動画の要約、チャプター、ハイライトを自動的に作成する「YouTube動画の要約」アプリの構築手順を説明します。フロントエンドにはReactを使用し、出力タイプの選択やタイムスタンプ付きの結果表示を行います。

この記事の内容

No headings found on page

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

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

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

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

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

2024/02/26

10分

記事へのリンクをコピー

マーケティングのバックグラウンドを持つ者として、私は常にインフルエンサーの世界に深く魅了されてきました。インフルエンサーであることの課題の一つは、素晴らしいコンテンツを作成し届けるためのインスピレーションを得続けることだと気づきました。これは、構成、重要なポイント、ハイライトを理解するために、他人が作成した数多くのビデオを視聴する必要があるYouTubeインフルエンサーにとって、特に当てはまります。 

Twelve LabsのGenerate APIを知ったとき、私はすぐにこの課題を解決する可能性を見出しました。このAPIを活用して、YouTubeビデオの要約、チャプター、ハイライトを生成することを目的としたシンプルなアプリの作成に着手しました。このアプリは包括的なレポートを作成し、各ビデオの構造化された分析を提供します。これにより、コンテンツ分析プロセスが合理化されるだけでなく、思考の整理も容易になります。

前置きはこれくらいにして、早速使ってみましょう!

前提条件

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

  • このアプリのすべてのファイルを含むリポジトリは、GitHubで入手できます。

  • (推奨)JavaScript、Node、React、React Queryに関する基本的な知識。これらに馴染みがなくても心配はいりません。この投稿から得られる重要なポイントは、このアプリがどのようにTwelve Labs APIを利用しているかを確認することです!

アプリの構成について

アプリは、SummarizeVideo、VideoUrlUploadForm、Video、InputForm、Resultという5つの主要なコンポーネントで構成されています。 

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

  • VideoUrlUploadForm: YouTubeビデオのURLを受け取り、TwelveLabs APIを使用してビデオをインデックスに登録するシンプルなフォームです。インデックス登録中のビデオや、登録が完了するまでのインデックス登録タスクのステータスも表示されます。 

  • Video: 指定されたURLのビデオを表示します。3つの異なるコンポーネント内で再利用されます。

  • InputForm: 「要約(Summary)」、「チャプター(Chapters)」、「ハイライト(Highlights)」の3つのチェックボックスで構成されるフォームです。ユーザーは各フィールドのチェックを入れたり外したりできます。 

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

このアプリには、すべてのAPI呼び出しを含むコードを格納するサーバーと、状態、キャッシュ、およびデータの取得を管理するためのカスタムReact QueryフックのセットであるapiHooks.jsも含まれています。

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

Twelve Labs APIとの連携方法

1 - インデックスの最新ビデオの表示

要約の生成において、このアプリは1つのビデオ(インデックスの中で最も新しくアップロードされたビデオ)のみを処理します。そのため、マウント時、アプリはデフォルトで指定されたインデックスの最新のビデオを表示します。仕組みは以下の通りです。 

  1. App.jsで指定されたインデックスのすべてのビデオを取得する(GET Videos

  2. レスポンスから最初のビデオのIDを抽出し、SummarizeVideo.jsに渡す

  3. ビデオIDを使用してビデオの詳細を取得し、ビデオのソースURLを抽出してVideo.jsに渡す(GET Video

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

1.1 - App.jsで指定されたインデックスのすべてのビデオを取得する(GET Videos)

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

Server.js (line 52 - 72)

/** ビデオの取得 */

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 });
 }
});

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

{
	"data": [
		{
			"_id": "65caf0fa48db9fa780cb3fc2",
			"created_at": "2024-02-13T04:32:54Z",
			"updated_at": "2024-02-13T04:32:58Z",
			"indexed_at": "2024-02-13T04:40:15Z",
			"metadata": {
				"duration": 130,
				"engine_ids": [
					"pegasus1",
					"marengo2.5"
				],
				"filename": "Adidas CEO Herbert Hainer: How I Work",
				"fps": 30,
				"height": 720,
				"size": 11149582,
				"width": 1280
			},
			{},			
}

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

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

App.js (line 34 - 38)

           <SummarizeVideo
             index={apiConfig.INDEX_ID}
             videoId={videos.data[0]?._id || null} // IDの受け渡し
             refetchVideos={refetchVideos}
           &sol;

1.3 - ビデオIDを使用してビデオの詳細を取得し、ビデオのソースURLを抽出してVideo.jsに渡す(GET Video)

ビデオを取得した前のステップと同様に、ビデオの詳細を取得するために、サーバーにリクエストを送信するReact QueryフックuseGetVideoを使用します。サーバーはその後、Twelve Labs APIに対してGETリクエストを送信し、特定のビデオの詳細情報を取得します。(💡詳細はAPIドキュメントを参照してください - GET Video

Server.js (line 74 - 95)

/** インデックスのビデオを取得 */
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 });
   }
 }
);


これにより、私たちが最も必要とする情報であるYouTube URLを含む、ビデオの詳細が返されます。お気づきかもしれませんが、GET VideosからはYouTube URLが取得できませんでした。これが、この段階でGET Videoリクエストを行っている理由です。

 {
	"_id": "65caf0fa48db9fa780cb3fc2",
	"created_at": "2024-02-13T04:32:54Z",
	"updated_at": "2024-02-13T04:32:58Z",
	"indexed_at": "2024-02-13T04:40:15Z",
	"metadata": {
		"duration": 130,
		"engine_ids": [
			"pegasus1",
			"marengo2.5"
		],
		"filename": "Adidas CEO Herbert Hainer: How I Work",
		"fps": 30,
		"height": 720,
		"size": 11149582,
		"video_title": "Adidas CEO Herbert Hainer: How I Work",
		"width": 1280
	},
	"hls": {
		"video_url": "...",
		"thumbnail_urls": [
			"..."
		],
		"status": "COMPLETE",
		"updated_at": "2024-02-13T04:33:29.993Z"
	},
	"source": {
		"type": "youtube",
		"name": "The Wall Street Journal",
		"url": "https://www.youtube.com/watch?v=sHD0YxASbGQ" // ここです!
	}
}

返されたデータ(ビデオ)に基づいて、そのURLをVideoコンポーネントに渡し、そのURLを使用してReact Playerがレンダリングされます。 

SummarizeVideo.js (line 101 - 105)

     {video && (
             <Video
               url={video.source?.url} // URLの受け渡し
               width={"381px"}
               height={"214px"}

2 - YouTube URLを使用したビデオのアップロードとインデックス作成

このアプリでは、APIバージョン1.2のおかげで、YouTubeのURLを送信するだけで簡単にビデオをアップロードし、インデックスを作成することができます。ビデオのインデックス作成リクエスト(これを「タスク」と呼びます)を送信すると、処理の進行状況を受け取ることができます。また、インデックス作成プロセス中に動画を表示するように実装したため、ユーザーは処理が進行している間に動画を確認し、視聴することができます。 

  1. VideoUrlUploadForm.jsでYouTubeのURLを使用してビデオインデックス作成タスクを作成する(POST Task

  2. VideoUrlUploadForm.jsでビデオ情報を取得してビデオを表示する(*これにはytdl-coreライブラリを使用しました) 

  3. Task.jsでインデックス作成タスクの進行状況を受け取って表示する(GET Task

各ステップを一つずつ見ていきましょう。

2.1 - VideoUrlUploadForm.jsでYouTubeのURLを使用してビデオインデックス作成タスクを作成する

ユーザーがYouTubeのURLを入力してvideoUrlUploadFormを送信すると、taskVideoが設定されます。taskVideoが存在する場合にindexYouTubeVideoが実行されるよう、useEffectを追加しました。 

indexYouTubeVideoはサーバーにPOSTリクエストを送信し、サーバーはTwelve Labs APIの「/tasks/external-provider」エンドポイントにPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Task

Server.js (line 134 - 152)

/** 分析用にYouTubeビデオのインディクスを作成し、タスクIDを返す */
app.post("/index", async (request, response, next) => {
 const options = {
   method: "POST",
   url: `${API_BASE_URL}/tasks/external-provider`,
   headers: { ...HEADERS, accept: "application/json" },
   data: request.body.body,
 };


 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": "65a9df3f627beda40b8dfa56"
}

2.2 - VideoUrlUploadForm.jsでビデオ情報を取得してビデオを表示する

ビデオタスクのインデックス作成が行われている間、対象のビデオをユーザーに表示します。 

VideoUrlUploadForm.js (line 120 - 126)

{taskVideo && (
           <div className="videoUrlUploadForm__taskVideoWrapper">
             <Video
               url={taskVideo.video_url}
               width={"381px"}
               height={"214px"}

ユーザーがYouTubeのURLを入力してフォームを送信すると、getVideoInfofetchVideoInfo(React Queryフック)を通じてビデオの情報を取得し、その情報でTaskVideoを設定します。 

VideoUrlUploadForm.js (line 74 - 88)

/** ビデオの情報を取得し、それをタスクとして設定する */
 async function handleSubmit(evt) {
   evt.preventDefault();
   try {
     if (!videoUrl?.trim()) {
       throw new Error("Please enter a valid video URL");
     }
     const videoInfo = await getVideoInfo(videoUrl); // ビデオ情報の取得
     setTaskVideo(videoInfo); // 取得したビデオ情報でTaskVideoを設定
     inputRef.current.value = "";
     resetPrompts();
   } catch (error) {
     setError(error.message);
   }
 }

ビデオ情報の取得には、ytdl-coreライブラリのgetURLVideoIDおよびgetBasicInfoを使用しています。 

Server.js (line 119 - 132)

/** ytdlを使用してYouTube URLからビデオ情報を取得 */
app.get("/video-info", async (request, response, next) => {
 try {
   let url = request.query.url;
   const videoId = ytdl.getURLVideoID(url);
   const videoInfo = await ytdl.getBasicInfo(videoId);
   response.json(videoInfo.videoDetails);
 } catch (error) {
   const status = error.response?.status || 500;
   const message =
     error.response?.data?.message || "Error getting info of a video";
   return next({ status, message });
 }
});

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

先ほどの「/index」へのPOSTリクエストがタスクIDを返したことを覚えていますか?このタスクIDを使用してタスクの詳細を取得し、ユーザーに対してタスクのステータスを更新し続けます。 

そのため、taskIdが存在する場合にのみ、Taskコンポーネントがレンダリングされます。 

VideoUrlUploadForm.js (line 130 - 137)

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

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

server.js (line 154 - 171)

/** 特定のインデックス作成タスクのステータスを確認 */
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": "65a9fc79627beda40b8dfa7b",
	"index_id": "653c0592480f870fb3bb01be",
	"video_id": "65a9fc7e4981af6e637c8e59",
	"status": "indexing",
	"metadata": {...},
	"created_at": "2024-01-19T04:37:13.724Z",
	"updated_at": "2024-01-19T04:41:10.606Z",
	"estimated_time": "2024-01-19T04:41:36.601Z",
	"type": "index_task_info",
	"process": {
		"upload_percentage": 0,
		"remain_seconds": 0
	},
	"hls": {...	}
}

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

apiHooks.js (line 128 - 142)

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 - ユーザー入力の受信と結果の生成・表示

ここがまさに核心であり、最も楽しい部分です。それは、要約、チャプター、ハイライトの生成です!ユーザーの入力を受け取った後、Twelve Labs APIのsummarizeエンドポイントを使用して、ビデオの要約テキスト、チャプター、およびハイライトを生成します。 

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

  2. 各フィールドプロンプトへのユーザー入力に基づいて、Result.js内で「/summary」API呼び出しを行う(POST Summaries, chapters, or highlights

  3. Result.jsに結果を表示する

各ステップを詳しく見ていきましょう。

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

InputFormは、要約、チャプター、ハイライトの3つのチェックボックスフィールドで構成されるシンプルなフォームです。ユーザーが各フィールドを有効または無効にするたびに、対応するフィールドプロンプトにtypeプロパティが設定されます。これは、次のステップで「/summary」にリクエストを送信するために「type」プロパティが必要になるためです。 

InputForm.js (line 33 - 49)

   if (summaryRef.current?.checked) {
     setField1Prompt({ type: field1 });
   } else {
     setField1Prompt(null);
   }


   if (chaptersRef.current?.checked) {
     setField2Prompt({ type: field2 });
   } else {
     setField2Prompt(null);
   }


   if (highlightsRef.current?.checked) {
     setField3Prompt({ type: field3 });
   } else {
     setField3Prompt(null);
   }

3.2 - 各フィールドプロンプトへのユーザー入力に基づいて、Result.js内で「/summary」API呼び出しを行う(POST Summaries, chapters, or highlights)

フォームが送信され、有効なビデオIDとフィールドプロンプト(type)が利用可能になると、Result.jsからuseGenerateフックが呼び出されます。その後、このフックはTwelve Labs APIへのリクエストを処理するサーバーにリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Summaries, chapters, or highlights

server.js (line 97 - 117)

/** ビデオを要約する */
app.post("/videos/:videoId/summarize", async (request, response, next) => {
 const videoId = request.params.videoId;
 let type = request.body.data;


 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/summarize`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...type, 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 Summarizing a Video";
   return next({ status, message });
 }
});

これにより、「id」(レスポンスID)とタイプ名(「summary」、「chapters」、「highlights」のいずれか)を含むデータオブジェクトが返されます。「chapters」と「highlights」には、ビデオプレイヤーを活用して結果を表示できるように、ビデオの各チャプターやハイライトの開始時間と修理時間が含まれています。以下は「chapters」(チャプター)のレスポンス例です。

{
	"id": "5a3f3e65-206a-4877-a0c5-871050edf3dc",
	"chapters": [
		{
			"chapter_number": 0,
			"start": 0,
			"end": 30,
			"chapter_title": "The Morning Routine",
			"chapter_summary": "The video starts with the man discussing his morning routine, including waking up between 6:30 and 7 am and having a cup of coffee…."
		},
           {...},
            
            	]
}

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

前述のステップから得られたレスポンスに基づいて、結果がResultコンポーネントに表示されます。先に述べたように、「chapters」と「highlights」については、それぞれの結果が対応するビデオの該当部分および要約テキストとともにレンダリングされます。  

まとめ

Twelve Labsの新しい「/summarize」エンドポイントを使用すると、ビデオの要約テキスト、チャプター、およびハイライトを簡単に作成することができます。APIがYouTubeのURLによる直接のビデオインデックス作成をサポートするようになったことで、このパフルな技術をYouTubeビデオに適用するのがはるかに容易になりました。私のユースケースが皆様にインスピレーションを与え、ご自身で試してみるきっかけになることを願っています。ハッピーコーディング!

次のステップ

マーケティングのバックグラウンドを持つ者として、私は常にインフルエンサーの世界に深く魅了されてきました。インフルエンサーであることの課題の一つは、素晴らしいコンテンツを作成し届けるためのインスピレーションを得続けることだと気づきました。これは、構成、重要なポイント、ハイライトを理解するために、他人が作成した数多くのビデオを視聴する必要があるYouTubeインフルエンサーにとって、特に当てはまります。 

Twelve LabsのGenerate APIを知ったとき、私はすぐにこの課題を解決する可能性を見出しました。このAPIを活用して、YouTubeビデオの要約、チャプター、ハイライトを生成することを目的としたシンプルなアプリの作成に着手しました。このアプリは包括的なレポートを作成し、各ビデオの構造化された分析を提供します。これにより、コンテンツ分析プロセスが合理化されるだけでなく、思考の整理も容易になります。

前置きはこれくらいにして、早速使ってみましょう!

前提条件

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

  • このアプリのすべてのファイルを含むリポジトリは、GitHubで入手できます。

  • (推奨)JavaScript、Node、React、React Queryに関する基本的な知識。これらに馴染みがなくても心配はいりません。この投稿から得られる重要なポイントは、このアプリがどのようにTwelve Labs APIを利用しているかを確認することです!

アプリの構成について

アプリは、SummarizeVideo、VideoUrlUploadForm、Video、InputForm、Resultという5つの主要なコンポーネントで構成されています。 

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

  • VideoUrlUploadForm: YouTubeビデオのURLを受け取り、TwelveLabs APIを使用してビデオをインデックスに登録するシンプルなフォームです。インデックス登録中のビデオや、登録が完了するまでのインデックス登録タスクのステータスも表示されます。 

  • Video: 指定されたURLのビデオを表示します。3つの異なるコンポーネント内で再利用されます。

  • InputForm: 「要約(Summary)」、「チャプター(Chapters)」、「ハイライト(Highlights)」の3つのチェックボックスで構成されるフォームです。ユーザーは各フィールドのチェックを入れたり外したりできます。 

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

このアプリには、すべてのAPI呼び出しを含むコードを格納するサーバーと、状態、キャッシュ、およびデータの取得を管理するためのカスタムReact QueryフックのセットであるapiHooks.jsも含まれています。

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

Twelve Labs APIとの連携方法

1 - インデックスの最新ビデオの表示

要約の生成において、このアプリは1つのビデオ(インデックスの中で最も新しくアップロードされたビデオ)のみを処理します。そのため、マウント時、アプリはデフォルトで指定されたインデックスの最新のビデオを表示します。仕組みは以下の通りです。 

  1. App.jsで指定されたインデックスのすべてのビデオを取得する(GET Videos

  2. レスポンスから最初のビデオのIDを抽出し、SummarizeVideo.jsに渡す

  3. ビデオIDを使用してビデオの詳細を取得し、ビデオのソースURLを抽出してVideo.jsに渡す(GET Video

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

1.1 - App.jsで指定されたインデックスのすべてのビデオを取得する(GET Videos)

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

Server.js (line 52 - 72)

/** ビデオの取得 */

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 });
 }
});

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

{
	"data": [
		{
			"_id": "65caf0fa48db9fa780cb3fc2",
			"created_at": "2024-02-13T04:32:54Z",
			"updated_at": "2024-02-13T04:32:58Z",
			"indexed_at": "2024-02-13T04:40:15Z",
			"metadata": {
				"duration": 130,
				"engine_ids": [
					"pegasus1",
					"marengo2.5"
				],
				"filename": "Adidas CEO Herbert Hainer: How I Work",
				"fps": 30,
				"height": 720,
				"size": 11149582,
				"width": 1280
			},
			{},			
}

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

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

App.js (line 34 - 38)

           <SummarizeVideo
             index={apiConfig.INDEX_ID}
             videoId={videos.data[0]?._id || null} // IDの受け渡し
             refetchVideos={refetchVideos}
           &sol;

1.3 - ビデオIDを使用してビデオの詳細を取得し、ビデオのソースURLを抽出してVideo.jsに渡す(GET Video)

ビデオを取得した前のステップと同様に、ビデオの詳細を取得するために、サーバーにリクエストを送信するReact QueryフックuseGetVideoを使用します。サーバーはその後、Twelve Labs APIに対してGETリクエストを送信し、特定のビデオの詳細情報を取得します。(💡詳細はAPIドキュメントを参照してください - GET Video

Server.js (line 74 - 95)

/** インデックスのビデオを取得 */
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 });
   }
 }
);


これにより、私たちが最も必要とする情報であるYouTube URLを含む、ビデオの詳細が返されます。お気づきかもしれませんが、GET VideosからはYouTube URLが取得できませんでした。これが、この段階でGET Videoリクエストを行っている理由です。

 {
	"_id": "65caf0fa48db9fa780cb3fc2",
	"created_at": "2024-02-13T04:32:54Z",
	"updated_at": "2024-02-13T04:32:58Z",
	"indexed_at": "2024-02-13T04:40:15Z",
	"metadata": {
		"duration": 130,
		"engine_ids": [
			"pegasus1",
			"marengo2.5"
		],
		"filename": "Adidas CEO Herbert Hainer: How I Work",
		"fps": 30,
		"height": 720,
		"size": 11149582,
		"video_title": "Adidas CEO Herbert Hainer: How I Work",
		"width": 1280
	},
	"hls": {
		"video_url": "...",
		"thumbnail_urls": [
			"..."
		],
		"status": "COMPLETE",
		"updated_at": "2024-02-13T04:33:29.993Z"
	},
	"source": {
		"type": "youtube",
		"name": "The Wall Street Journal",
		"url": "https://www.youtube.com/watch?v=sHD0YxASbGQ" // ここです!
	}
}

返されたデータ(ビデオ)に基づいて、そのURLをVideoコンポーネントに渡し、そのURLを使用してReact Playerがレンダリングされます。 

SummarizeVideo.js (line 101 - 105)

     {video && (
             <Video
               url={video.source?.url} // URLの受け渡し
               width={"381px"}
               height={"214px"}

2 - YouTube URLを使用したビデオのアップロードとインデックス作成

このアプリでは、APIバージョン1.2のおかげで、YouTubeのURLを送信するだけで簡単にビデオをアップロードし、インデックスを作成することができます。ビデオのインデックス作成リクエスト(これを「タスク」と呼びます)を送信すると、処理の進行状況を受け取ることができます。また、インデックス作成プロセス中に動画を表示するように実装したため、ユーザーは処理が進行している間に動画を確認し、視聴することができます。 

  1. VideoUrlUploadForm.jsでYouTubeのURLを使用してビデオインデックス作成タスクを作成する(POST Task

  2. VideoUrlUploadForm.jsでビデオ情報を取得してビデオを表示する(*これにはytdl-coreライブラリを使用しました) 

  3. Task.jsでインデックス作成タスクの進行状況を受け取って表示する(GET Task

各ステップを一つずつ見ていきましょう。

2.1 - VideoUrlUploadForm.jsでYouTubeのURLを使用してビデオインデックス作成タスクを作成する

ユーザーがYouTubeのURLを入力してvideoUrlUploadFormを送信すると、taskVideoが設定されます。taskVideoが存在する場合にindexYouTubeVideoが実行されるよう、useEffectを追加しました。 

indexYouTubeVideoはサーバーにPOSTリクエストを送信し、サーバーはTwelve Labs APIの「/tasks/external-provider」エンドポイントにPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Task

Server.js (line 134 - 152)

/** 分析用にYouTubeビデオのインディクスを作成し、タスクIDを返す */
app.post("/index", async (request, response, next) => {
 const options = {
   method: "POST",
   url: `${API_BASE_URL}/tasks/external-provider`,
   headers: { ...HEADERS, accept: "application/json" },
   data: request.body.body,
 };


 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": "65a9df3f627beda40b8dfa56"
}

2.2 - VideoUrlUploadForm.jsでビデオ情報を取得してビデオを表示する

ビデオタスクのインデックス作成が行われている間、対象のビデオをユーザーに表示します。 

VideoUrlUploadForm.js (line 120 - 126)

{taskVideo && (
           <div className="videoUrlUploadForm__taskVideoWrapper">
             <Video
               url={taskVideo.video_url}
               width={"381px"}
               height={"214px"}

ユーザーがYouTubeのURLを入力してフォームを送信すると、getVideoInfofetchVideoInfo(React Queryフック)を通じてビデオの情報を取得し、その情報でTaskVideoを設定します。 

VideoUrlUploadForm.js (line 74 - 88)

/** ビデオの情報を取得し、それをタスクとして設定する */
 async function handleSubmit(evt) {
   evt.preventDefault();
   try {
     if (!videoUrl?.trim()) {
       throw new Error("Please enter a valid video URL");
     }
     const videoInfo = await getVideoInfo(videoUrl); // ビデオ情報の取得
     setTaskVideo(videoInfo); // 取得したビデオ情報でTaskVideoを設定
     inputRef.current.value = "";
     resetPrompts();
   } catch (error) {
     setError(error.message);
   }
 }

ビデオ情報の取得には、ytdl-coreライブラリのgetURLVideoIDおよびgetBasicInfoを使用しています。 

Server.js (line 119 - 132)

/** ytdlを使用してYouTube URLからビデオ情報を取得 */
app.get("/video-info", async (request, response, next) => {
 try {
   let url = request.query.url;
   const videoId = ytdl.getURLVideoID(url);
   const videoInfo = await ytdl.getBasicInfo(videoId);
   response.json(videoInfo.videoDetails);
 } catch (error) {
   const status = error.response?.status || 500;
   const message =
     error.response?.data?.message || "Error getting info of a video";
   return next({ status, message });
 }
});

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

先ほどの「/index」へのPOSTリクエストがタスクIDを返したことを覚えていますか?このタスクIDを使用してタスクの詳細を取得し、ユーザーに対してタスクのステータスを更新し続けます。 

そのため、taskIdが存在する場合にのみ、Taskコンポーネントがレンダリングされます。 

VideoUrlUploadForm.js (line 130 - 137)

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

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

server.js (line 154 - 171)

/** 特定のインデックス作成タスクのステータスを確認 */
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": "65a9fc79627beda40b8dfa7b",
	"index_id": "653c0592480f870fb3bb01be",
	"video_id": "65a9fc7e4981af6e637c8e59",
	"status": "indexing",
	"metadata": {...},
	"created_at": "2024-01-19T04:37:13.724Z",
	"updated_at": "2024-01-19T04:41:10.606Z",
	"estimated_time": "2024-01-19T04:41:36.601Z",
	"type": "index_task_info",
	"process": {
		"upload_percentage": 0,
		"remain_seconds": 0
	},
	"hls": {...	}
}

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

apiHooks.js (line 128 - 142)

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 - ユーザー入力の受信と結果の生成・表示

ここがまさに核心であり、最も楽しい部分です。それは、要約、チャプター、ハイライトの生成です!ユーザーの入力を受け取った後、Twelve Labs APIのsummarizeエンドポイントを使用して、ビデオの要約テキスト、チャプター、およびハイライトを生成します。 

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

  2. 各フィールドプロンプトへのユーザー入力に基づいて、Result.js内で「/summary」API呼び出しを行う(POST Summaries, chapters, or highlights

  3. Result.jsに結果を表示する

各ステップを詳しく見ていきましょう。

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

InputFormは、要約、チャプター、ハイライトの3つのチェックボックスフィールドで構成されるシンプルなフォームです。ユーザーが各フィールドを有効または無効にするたびに、対応するフィールドプロンプトにtypeプロパティが設定されます。これは、次のステップで「/summary」にリクエストを送信するために「type」プロパティが必要になるためです。 

InputForm.js (line 33 - 49)

   if (summaryRef.current?.checked) {
     setField1Prompt({ type: field1 });
   } else {
     setField1Prompt(null);
   }


   if (chaptersRef.current?.checked) {
     setField2Prompt({ type: field2 });
   } else {
     setField2Prompt(null);
   }


   if (highlightsRef.current?.checked) {
     setField3Prompt({ type: field3 });
   } else {
     setField3Prompt(null);
   }

3.2 - 各フィールドプロンプトへのユーザー入力に基づいて、Result.js内で「/summary」API呼び出しを行う(POST Summaries, chapters, or highlights)

フォームが送信され、有効なビデオIDとフィールドプロンプト(type)が利用可能になると、Result.jsからuseGenerateフックが呼び出されます。その後、このフックはTwelve Labs APIへのリクエストを処理するサーバーにリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Summaries, chapters, or highlights

server.js (line 97 - 117)

/** ビデオを要約する */
app.post("/videos/:videoId/summarize", async (request, response, next) => {
 const videoId = request.params.videoId;
 let type = request.body.data;


 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/summarize`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...type, 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 Summarizing a Video";
   return next({ status, message });
 }
});

これにより、「id」(レスポンスID)とタイプ名(「summary」、「chapters」、「highlights」のいずれか)を含むデータオブジェクトが返されます。「chapters」と「highlights」には、ビデオプレイヤーを活用して結果を表示できるように、ビデオの各チャプターやハイライトの開始時間と修理時間が含まれています。以下は「chapters」(チャプター)のレスポンス例です。

{
	"id": "5a3f3e65-206a-4877-a0c5-871050edf3dc",
	"chapters": [
		{
			"chapter_number": 0,
			"start": 0,
			"end": 30,
			"chapter_title": "The Morning Routine",
			"chapter_summary": "The video starts with the man discussing his morning routine, including waking up between 6:30 and 7 am and having a cup of coffee…."
		},
           {...},
            
            	]
}

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

前述のステップから得られたレスポンスに基づいて、結果がResultコンポーネントに表示されます。先に述べたように、「chapters」と「highlights」については、それぞれの結果が対応するビデオの該当部分および要約テキストとともにレンダリングされます。  

まとめ

Twelve Labsの新しい「/summarize」エンドポイントを使用すると、ビデオの要約テキスト、チャプター、およびハイライトを簡単に作成することができます。APIがYouTubeのURLによる直接のビデオインデックス作成をサポートするようになったことで、このパフルな技術をYouTubeビデオに適用するのがはるかに容易になりました。私のユースケースが皆様にインスピレーションを与え、ご自身で試してみるきっかけになることを願っています。ハッピーコーディング!

次のステップ

マーケティングのバックグラウンドを持つ者として、私は常にインフルエンサーの世界に深く魅了されてきました。インフルエンサーであることの課題の一つは、素晴らしいコンテンツを作成し届けるためのインスピレーションを得続けることだと気づきました。これは、構成、重要なポイント、ハイライトを理解するために、他人が作成した数多くのビデオを視聴する必要があるYouTubeインフルエンサーにとって、特に当てはまります。 

Twelve LabsのGenerate APIを知ったとき、私はすぐにこの課題を解決する可能性を見出しました。このAPIを活用して、YouTubeビデオの要約、チャプター、ハイライトを生成することを目的としたシンプルなアプリの作成に着手しました。このアプリは包括的なレポートを作成し、各ビデオの構造化された分析を提供します。これにより、コンテンツ分析プロセスが合理化されるだけでなく、思考の整理も容易になります。

前置きはこれくらいにして、早速使ってみましょう!

前提条件

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

  • このアプリのすべてのファイルを含むリポジトリは、GitHubで入手できます。

  • (推奨)JavaScript、Node、React、React Queryに関する基本的な知識。これらに馴染みがなくても心配はいりません。この投稿から得られる重要なポイントは、このアプリがどのようにTwelve Labs APIを利用しているかを確認することです!

アプリの構成について

アプリは、SummarizeVideo、VideoUrlUploadForm、Video、InputForm、Resultという5つの主要なコンポーネントで構成されています。 

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

  • VideoUrlUploadForm: YouTubeビデオのURLを受け取り、TwelveLabs APIを使用してビデオをインデックスに登録するシンプルなフォームです。インデックス登録中のビデオや、登録が完了するまでのインデックス登録タスクのステータスも表示されます。 

  • Video: 指定されたURLのビデオを表示します。3つの異なるコンポーネント内で再利用されます。

  • InputForm: 「要約(Summary)」、「チャプター(Chapters)」、「ハイライト(Highlights)」の3つのチェックボックスで構成されるフォームです。ユーザーは各フィールドのチェックを入れたり外したりできます。 

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

このアプリには、すべてのAPI呼び出しを含むコードを格納するサーバーと、状態、キャッシュ、およびデータの取得を管理するためのカスタムReact QueryフックのセットであるapiHooks.jsも含まれています。

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

Twelve Labs APIとの連携方法

1 - インデックスの最新ビデオの表示

要約の生成において、このアプリは1つのビデオ(インデックスの中で最も新しくアップロードされたビデオ)のみを処理します。そのため、マウント時、アプリはデフォルトで指定されたインデックスの最新のビデオを表示します。仕組みは以下の通りです。 

  1. App.jsで指定されたインデックスのすべてのビデオを取得する(GET Videos

  2. レスポンスから最初のビデオのIDを抽出し、SummarizeVideo.jsに渡す

  3. ビデオIDを使用してビデオの詳細を取得し、ビデオのソースURLを抽出してVideo.jsに渡す(GET Video

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

1.1 - App.jsで指定されたインデックスのすべてのビデオを取得する(GET Videos)

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

Server.js (line 52 - 72)

/** ビデオの取得 */

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 });
 }
});

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

{
	"data": [
		{
			"_id": "65caf0fa48db9fa780cb3fc2",
			"created_at": "2024-02-13T04:32:54Z",
			"updated_at": "2024-02-13T04:32:58Z",
			"indexed_at": "2024-02-13T04:40:15Z",
			"metadata": {
				"duration": 130,
				"engine_ids": [
					"pegasus1",
					"marengo2.5"
				],
				"filename": "Adidas CEO Herbert Hainer: How I Work",
				"fps": 30,
				"height": 720,
				"size": 11149582,
				"width": 1280
			},
			{},			
}

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

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

App.js (line 34 - 38)

           <SummarizeVideo
             index={apiConfig.INDEX_ID}
             videoId={videos.data[0]?._id || null} // IDの受け渡し
             refetchVideos={refetchVideos}
           &sol;

1.3 - ビデオIDを使用してビデオの詳細を取得し、ビデオのソースURLを抽出してVideo.jsに渡す(GET Video)

ビデオを取得した前のステップと同様に、ビデオの詳細を取得するために、サーバーにリクエストを送信するReact QueryフックuseGetVideoを使用します。サーバーはその後、Twelve Labs APIに対してGETリクエストを送信し、特定のビデオの詳細情報を取得します。(💡詳細はAPIドキュメントを参照してください - GET Video

Server.js (line 74 - 95)

/** インデックスのビデオを取得 */
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 });
   }
 }
);


これにより、私たちが最も必要とする情報であるYouTube URLを含む、ビデオの詳細が返されます。お気づきかもしれませんが、GET VideosからはYouTube URLが取得できませんでした。これが、この段階でGET Videoリクエストを行っている理由です。

 {
	"_id": "65caf0fa48db9fa780cb3fc2",
	"created_at": "2024-02-13T04:32:54Z",
	"updated_at": "2024-02-13T04:32:58Z",
	"indexed_at": "2024-02-13T04:40:15Z",
	"metadata": {
		"duration": 130,
		"engine_ids": [
			"pegasus1",
			"marengo2.5"
		],
		"filename": "Adidas CEO Herbert Hainer: How I Work",
		"fps": 30,
		"height": 720,
		"size": 11149582,
		"video_title": "Adidas CEO Herbert Hainer: How I Work",
		"width": 1280
	},
	"hls": {
		"video_url": "...",
		"thumbnail_urls": [
			"..."
		],
		"status": "COMPLETE",
		"updated_at": "2024-02-13T04:33:29.993Z"
	},
	"source": {
		"type": "youtube",
		"name": "The Wall Street Journal",
		"url": "https://www.youtube.com/watch?v=sHD0YxASbGQ" // ここです!
	}
}

返されたデータ(ビデオ)に基づいて、そのURLをVideoコンポーネントに渡し、そのURLを使用してReact Playerがレンダリングされます。 

SummarizeVideo.js (line 101 - 105)

     {video && (
             <Video
               url={video.source?.url} // URLの受け渡し
               width={"381px"}
               height={"214px"}

2 - YouTube URLを使用したビデオのアップロードとインデックス作成

このアプリでは、APIバージョン1.2のおかげで、YouTubeのURLを送信するだけで簡単にビデオをアップロードし、インデックスを作成することができます。ビデオのインデックス作成リクエスト(これを「タスク」と呼びます)を送信すると、処理の進行状況を受け取ることができます。また、インデックス作成プロセス中に動画を表示するように実装したため、ユーザーは処理が進行している間に動画を確認し、視聴することができます。 

  1. VideoUrlUploadForm.jsでYouTubeのURLを使用してビデオインデックス作成タスクを作成する(POST Task

  2. VideoUrlUploadForm.jsでビデオ情報を取得してビデオを表示する(*これにはytdl-coreライブラリを使用しました) 

  3. Task.jsでインデックス作成タスクの進行状況を受け取って表示する(GET Task

各ステップを一つずつ見ていきましょう。

2.1 - VideoUrlUploadForm.jsでYouTubeのURLを使用してビデオインデックス作成タスクを作成する

ユーザーがYouTubeのURLを入力してvideoUrlUploadFormを送信すると、taskVideoが設定されます。taskVideoが存在する場合にindexYouTubeVideoが実行されるよう、useEffectを追加しました。 

indexYouTubeVideoはサーバーにPOSTリクエストを送信し、サーバーはTwelve Labs APIの「/tasks/external-provider」エンドポイントにPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Task

Server.js (line 134 - 152)

/** 分析用にYouTubeビデオのインディクスを作成し、タスクIDを返す */
app.post("/index", async (request, response, next) => {
 const options = {
   method: "POST",
   url: `${API_BASE_URL}/tasks/external-provider`,
   headers: { ...HEADERS, accept: "application/json" },
   data: request.body.body,
 };


 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": "65a9df3f627beda40b8dfa56"
}

2.2 - VideoUrlUploadForm.jsでビデオ情報を取得してビデオを表示する

ビデオタスクのインデックス作成が行われている間、対象のビデオをユーザーに表示します。 

VideoUrlUploadForm.js (line 120 - 126)

{taskVideo && (
           <div className="videoUrlUploadForm__taskVideoWrapper">
             <Video
               url={taskVideo.video_url}
               width={"381px"}
               height={"214px"}

ユーザーがYouTubeのURLを入力してフォームを送信すると、getVideoInfofetchVideoInfo(React Queryフック)を通じてビデオの情報を取得し、その情報でTaskVideoを設定します。 

VideoUrlUploadForm.js (line 74 - 88)

/** ビデオの情報を取得し、それをタスクとして設定する */
 async function handleSubmit(evt) {
   evt.preventDefault();
   try {
     if (!videoUrl?.trim()) {
       throw new Error("Please enter a valid video URL");
     }
     const videoInfo = await getVideoInfo(videoUrl); // ビデオ情報の取得
     setTaskVideo(videoInfo); // 取得したビデオ情報でTaskVideoを設定
     inputRef.current.value = "";
     resetPrompts();
   } catch (error) {
     setError(error.message);
   }
 }

ビデオ情報の取得には、ytdl-coreライブラリのgetURLVideoIDおよびgetBasicInfoを使用しています。 

Server.js (line 119 - 132)

/** ytdlを使用してYouTube URLからビデオ情報を取得 */
app.get("/video-info", async (request, response, next) => {
 try {
   let url = request.query.url;
   const videoId = ytdl.getURLVideoID(url);
   const videoInfo = await ytdl.getBasicInfo(videoId);
   response.json(videoInfo.videoDetails);
 } catch (error) {
   const status = error.response?.status || 500;
   const message =
     error.response?.data?.message || "Error getting info of a video";
   return next({ status, message });
 }
});

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

先ほどの「/index」へのPOSTリクエストがタスクIDを返したことを覚えていますか?このタスクIDを使用してタスクの詳細を取得し、ユーザーに対してタスクのステータスを更新し続けます。 

そのため、taskIdが存在する場合にのみ、Taskコンポーネントがレンダリングされます。 

VideoUrlUploadForm.js (line 130 - 137)

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

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

server.js (line 154 - 171)

/** 特定のインデックス作成タスクのステータスを確認 */
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": "65a9fc79627beda40b8dfa7b",
	"index_id": "653c0592480f870fb3bb01be",
	"video_id": "65a9fc7e4981af6e637c8e59",
	"status": "indexing",
	"metadata": {...},
	"created_at": "2024-01-19T04:37:13.724Z",
	"updated_at": "2024-01-19T04:41:10.606Z",
	"estimated_time": "2024-01-19T04:41:36.601Z",
	"type": "index_task_info",
	"process": {
		"upload_percentage": 0,
		"remain_seconds": 0
	},
	"hls": {...	}
}

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

apiHooks.js (line 128 - 142)

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 - ユーザー入力の受信と結果の生成・表示

ここがまさに核心であり、最も楽しい部分です。それは、要約、チャプター、ハイライトの生成です!ユーザーの入力を受け取った後、Twelve Labs APIのsummarizeエンドポイントを使用して、ビデオの要約テキスト、チャプター、およびハイライトを生成します。 

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

  2. 各フィールドプロンプトへのユーザー入力に基づいて、Result.js内で「/summary」API呼び出しを行う(POST Summaries, chapters, or highlights

  3. Result.jsに結果を表示する

各ステップを詳しく見ていきましょう。

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

InputFormは、要約、チャプター、ハイライトの3つのチェックボックスフィールドで構成されるシンプルなフォームです。ユーザーが各フィールドを有効または無効にするたびに、対応するフィールドプロンプトにtypeプロパティが設定されます。これは、次のステップで「/summary」にリクエストを送信するために「type」プロパティが必要になるためです。 

InputForm.js (line 33 - 49)

   if (summaryRef.current?.checked) {
     setField1Prompt({ type: field1 });
   } else {
     setField1Prompt(null);
   }


   if (chaptersRef.current?.checked) {
     setField2Prompt({ type: field2 });
   } else {
     setField2Prompt(null);
   }


   if (highlightsRef.current?.checked) {
     setField3Prompt({ type: field3 });
   } else {
     setField3Prompt(null);
   }

3.2 - 各フィールドプロンプトへのユーザー入力に基づいて、Result.js内で「/summary」API呼び出しを行う(POST Summaries, chapters, or highlights)

フォームが送信され、有効なビデオIDとフィールドプロンプト(type)が利用可能になると、Result.jsからuseGenerateフックが呼び出されます。その後、このフックはTwelve Labs APIへのリクエストを処理するサーバーにリクエストを送信します。(💡詳細はAPIドキュメントを参照してください - POST Summaries, chapters, or highlights

server.js (line 97 - 117)

/** ビデオを要約する */
app.post("/videos/:videoId/summarize", async (request, response, next) => {
 const videoId = request.params.videoId;
 let type = request.body.data;


 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/summarize`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...type, 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 Summarizing a Video";
   return next({ status, message });
 }
});

これにより、「id」(レスポンスID)とタイプ名(「summary」、「chapters」、「highlights」のいずれか)を含むデータオブジェクトが返されます。「chapters」と「highlights」には、ビデオプレイヤーを活用して結果を表示できるように、ビデオの各チャプターやハイライトの開始時間と修理時間が含まれています。以下は「chapters」(チャプター)のレスポンス例です。

{
	"id": "5a3f3e65-206a-4877-a0c5-871050edf3dc",
	"chapters": [
		{
			"chapter_number": 0,
			"start": 0,
			"end": 30,
			"chapter_title": "The Morning Routine",
			"chapter_summary": "The video starts with the man discussing his morning routine, including waking up between 6:30 and 7 am and having a cup of coffee…."
		},
           {...},
            
            	]
}

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

前述のステップから得られたレスポンスに基づいて、結果がResultコンポーネントに表示されます。先に述べたように、「chapters」と「highlights」については、それぞれの結果が対応するビデオの該当部分および要約テキストとともにレンダリングされます。  

まとめ

Twelve Labsの新しい「/summarize」エンドポイントを使用すると、ビデオの要約テキスト、チャプター、およびハイライトを簡単に作成することができます。APIがYouTubeのURLによる直接のビデオインデックス作成をサポートするようになったことで、このパフルな技術をYouTubeビデオに適用するのがはるかに容易になりました。私のユースケースが皆様にインスピレーションを与え、ご自身で試してみるきっかけになることを願っています。ハッピーコーディング!

次のステップ