チュートリアル

動画からSNSコンテンツを簡単に作成

ミラン・キム

このチュートリアルでは、Twelve Labs Analyze APIを使用してソーシャルメディア・コンテンツ・ジェネレーターを構築する手順を説明します。これにより、インデックスされたあらゆる動画を、事前に設定されたプロンプトやユーザー独自の入力内容に基づいて、Instagramの投稿、Facebookのアップデート、X(旧Twitter)の投稿、ブログエントリーなど、各プラットフォームに最適化されたテキストコンテンツへと変換できます。

このチュートリアルでは、Twelve Labs Analyze APIを使用してソーシャルメディア・コンテンツ・ジェネレーターを構築する手順を説明します。これにより、インデックスされたあらゆる動画を、事前に設定されたプロンプトやユーザー独自の入力内容に基づいて、Instagramの投稿、Facebookのアップデート、X(旧Twitter)の投稿、ブログエントリーなど、各プラットフォームに最適化されたテキストコンテンツへと変換できます。

この記事の内容

No headings found on page

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

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

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

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

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

2024/05/12

8分

記事へのリンクをコピー

「ワンソース、マルチユース(1つのソースを多用途に活用する)」という概念をご存知ですか?コンテンツクリエイターやインフルエンサーにとって、異なるプラットフォーム向けにコンテンツを再利用してオーディエンスのエンゲージメントを最大化することは、非常に重要な戦略です。

この「ビデオのソーシャル投稿生成(Generate Social Posts for your Video)」ツールは、ビデオコンテンツクリエイターが、絵文字を多用した楽しいInstagram投稿であれ、詳細なブログ記事であれ、動画をテキストベースの形式に変換するのをサポートするために特別に設計されています。 

使い方はとても簡単です。アプリケーションに動画をアップロードし、投稿を作成したいソーシャルメディアプラットフォームのいずれかを選択するだけです。または、「箇条書きで要約を執筆する」といった独自の指示(プロンプト)や要件をカスタマイズして、希望するテキスト形式を指定することもできます。数回クリックするだけで、変換されたコンテンツの準備が整います。 

それでは、アプリ構築のステップバイステップのプロセスを始めていきましょう!


前提条件

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

  • このアプリに必要なすべてのファイルが含まれるリポジトリは、Githubで公開されています。

  • (推奨)JavaScript、TypeScript、Node、React、およびReact Queryに関する基本的な知識。


アプリの構成について

__wf_reserved_inherit

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

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

  • VideoUrlUploadForm: ビデオファイルをアップロードし、TwelveLabs APIを使ってインデックスを作成するためのシンプルなフォームを提供します。インデックス作成中のビデオを表示し、処理が完了するまでリアルタイムのステータス更新を提供します。

  • Video: 指定されたURLに基づいてビデオを表示します。アプリケーション全体の複数のコンポーネント内で再利用されます。

  • InputForm: 主要なソーシャルメディアプラットフォームを表すプリセットのラジオボタンと、ユーザーが動画から出力させたいテキストコンテンツの要件を指定できるテキストエリアで構成されています。 

  • Result: このコンポーネントは、Twelve Labsの「/generate」APIを活用し、ユーザーが選択したソーシャルプラットフォームや入力内容に基づいて、生成されたテキストコンテンツを表示します。

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

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

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


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

__wf_reserved_inherit

このアプリは、インデックスに最も新しくアップロードされた1本のビデオのみを処理対象とします。そのため、構築時(マウント時)に、デフォルトで指定されたインデックスの最新ビデオが表示されます。その連携プロセスは以下の通りです。

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

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

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

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

1.1 - App.tsx内の特定インデックスからすべてのビデオを取得する

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

Server.js (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.tsxon(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": "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を抽出し、GenerateSocialPosts.tsxに渡す

返されたビデオリストに基づいて、最初のビデオのIDをGenerateSocialPosts.tsxコンポーネントに送ります。

App.tsx (39 - 43行目)

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


1.3 - ビデオIDを使用して詳細を取得し、ビデオ画像URLを抽出してVideo.tsxに渡す

先述の全体ビデオ取得の手順と同様に、特定のビデオの詳細情報を取得するため、サーバーにリクエストするReact QueryフックのuseGetVideoを使用します。サーバーからTwelve Labs APIへGETリクエストを送信して詳細情報を取得します。(💡詳細はAPIドキュメントを参照 - GET Video

Server.js (75 - 96行目)

/** インデックスのビデオを取得 */

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は直接取得できませんでした。そのため、さらに深く探ってビデオ再生URLを抽出するために、ここで個別のGET Videoリクエストを実行しています。

{
	"_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を利用してロードして描画を行います。 

GenerateSocialPosts.tsx (110 - 116行目)

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

2 - ローカルにあるビデオファイルを使用したアップロードとインデックス作成

__wf_reserved_inherit

このアプリでは、ユーザーはローカルデバイスからビデオファイルを選択し、アップロードしてインデックス作成を実行できます。このアプリの想定ユーザーであるビデオコンテンツクリエイターが、手元のPCなどのローカル端末に完成した動画データを持っていることを想定しています。 

ビデオインデックス作成リクエスト(ここでは「タスク」と呼びます)を一度送信すると、処理の進行状況を取得し画面に表示できます。また、インデックスの進行中にもビデオを確認して視聴できるよう設定しました。なお、インデックス処理が始まって直後に再生URLが用意されるわけではなく、進捗が半分ほど進んだ段階で再生可能になります。 

  1. VideoUrlUploadForm.tsxにて、ローカルビデオファイルからインデックスタスクを生成する(POST Task

  2. Task.tsxにて、生成したタスクの進捗状況を受信し、ユーザーに表示する(GET Task

ステップごとに見てみましょう。

2.1 - VideoUrlUploadForm.tsxにてビデオファイルを使用したインデックスタスクを作る

ユーザーがローカル環境からビデオファイルを選択してフォームを送信すると、インデックス作成プロセスを開始します。indexYouTubeVideoが必要なリクエストデータ(言語、インデックスID、ビデオファイルなど)を形成して、サーバーへPOSTリクエストを送信します。その後、サーバーはTwelve Labs APIの「/tasks」エンドポイントへPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照 - POST Task

server.js (119 - 158行目)

/** ビデオのインデックスを作成し、タスクIDを返す */

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


   // Append data from 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 Video";
     return next({ status, message });
   }
 }
);

これにより、新規作成されたビデオインデックスタスクのIDが返されます。 

{
	"_id": "65e9f732bb29f13bdd6f305a"
}

2.2 - Task.tsxにてインデックスタスクの進捗を取得して表示する

前章で取得したタスクIDを元に進行状態の詳細を追い、UIを定期的にアップデートします。そのため、 taskIdが存在する際にはVideoFileUploadForm内で、このTaskコンポーネントを描画させるようにします。

VideoFileUploadForm.tsx (153行目)

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

Taskコンポーネントの中で、タスク情報をロードするために、サーバーへ通信するReact QueryフックであるuseGetTaskを使います。サーバー側の実装からTwelve Labs APIに問い合わせを投げます。(💡詳細はAPIドキュメントを参照 - GET Task

server.js (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.tsxon(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ミリ秒ごと(5秒ごと)に一度データを再取得し、リアルタイムで完了プロセスをレンダリングできるようにしています。下記のように、useQueryのrefetchIntervalオプションを活用しています。 

apiHooks.tsx (73 - 94行目)

export function useGetTask(taskId: string) {
 return useQuery<Task, Error, Task, [string, string]>({
   queryKey: [keys.TASK, taskId],
   queryFn: async (): Promise<Task> => {
     try {
       const response = await apiConfig.SERVER.get(`${apiConfig.TASKS_URL}/${taskId}`);
       const respData: Task = response.data;
       return respData;
     } catch (error) {
       console.error("Error fetching task:", error);
       throw error;
     }
   },
   refetchInterval: (query) => {
     const data = query.state.data;
     return data && (data.status === "ready" || data.status === "failed")
       ? false
       : 5000;
   },
   refetchIntervalInBackground: true,
 });
}

3 - ユーザーインプットを受け取り、記事コンテンツを生成・表示する

__wf_reserved_inherit

いよいよ一番面白く、コアな部分、すなわちビデオに基づいて任意のテキストを生成するブロックです!ユーザーインプットを受け取った後、Twelve Labs APIの「/analyze」エンドポイントを使って自由形式(オープンエンド)のテキストを生成させます。

  1. 主要なソーシャルメディアのプリセットのラジオボタンか、自由に入力できるテキストエリアからInputForm.tsxでユーザー入力を受け取る

  2. 入力に基づいて、Result.tsx内の「analyze」 APIを実行する(POST Open-ended texts

  3. Result.tsxコンポーネントの中で実行結果を出力する

詳しく各ステップを掘り下げていきます。

3.1 - Radio preset または textareaフォームより、入力を保持する

InputFormは、代表的なソーシャルプラットフォーム(Instagram、Facebook、X、blog、およびその他の要件カスタマイズ用の「Others」オプション)を処理する5つのラジオボタンで構成されたシンプルなUIです。

一般的なSNSのプリセットボタンを選んだ場合は、背景で調整された最適なシステム指令プロンプト(SNSごとに適切な文脈、形式指定など)がセットされ、result.tsx の APIに送信されます。「Others」を選択した場合は、自身でフォーカス設定したいお好みの要件を自由に設定できます。 

InputForm.tsx (76 - 107行目)

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
   event.preventDefault();
   let promptValue = "";
   let platformValue = "";


   if (instagramRef.current?.checked) {
     promptValue = "write a Instagram post with emojis, 100 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Instagram";
   } else if (facebookRef.current?.checked) {
     promptValue = "write a Facebook post with emojis, 150 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Facebook";
   } else if (xRef.current?.checked) {
     promptValue = "write a X (formerly Twitter) post with emojis, 50 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "X";
   } else if (blogRef.current?.checked) {
     promptValue = "write a blog post with details. Divide sections with subtitles. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Blog";
   } else if (textRadioRef.current?.checked) {
     const inputValue = textAreaRef.current?.value.trim();
     if (inputValue?.length && inputValue?.length > 0) {
       promptValue = inputValue;
       platformValue = `"${inputValue}"`;
     } else {
       setShowCheckWarning(true)



💡 プロンプトエンジニアリングのヒント

InstagramやFacebook、その他向けに提供している内部プロンプト定義をご覧ください。さらに詳しく知りたい方は、Twelve Labsにチューニングされたプロンプト最適化の公式ガイドも合わせて参照してください。

3.2 - 受信情報に基づいて「/analyze」APIをResult.tsxから発火する

フォームが送信され、ビデオIDおよび指示(プロンプト)が確定されると、Result.tsxからuseAnalyze などのフックが呼び出されます。その後、十二分に整えられたパラメーターで、バックエンド側からTwelve Labsの「/analyze」APIにPOSTリクエストを送ります。(💡詳細はAPIドキュメントを参照 - POST Open-ended analysis

server.tsx (98 - 116行目)

/** 動画の自由形式テキストを生成 */
app.post("/videos/:videoId/analyze", async (request, response, next) => {
 const videoId = request.params.videoId;
 let prompt = request.body.data;
 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/analyze`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...prompt, video_id: videoId, temperature: 0.3, stream: false },
   };
   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 Text";
   return next({ status, message });
 }
});


リクエストが成功すると、提供したプロンプトをベースに自動生成されたテキストを含む結果データ( data 属性)がオブジェクト形式で返されます。以下は「Instagram」を指定したときの出力データの一例です。

	id: '2403323b-b648-48a7-8897-95258e68e8a4',
    data: "👨‍🍳 ツナは猫の餌のためだけじゃない!🐱 今日は缶詰のツナを4つのおいしい料理にアレンジしてみました:🍚 野菜とスクランブルエッグがのったピリ辛ツナチャーハン、🍔 フレーバーたっぷりでルッコラをトッピングしたツナバーガー、🍝 ケッパーとレモンの皮を和えたツナパスタ、そして🍙 美味しいおやつの定番・ツナマヨおにぎり!🍲 どの料理もシンプルで栄養満点、どんな食事にもぴったりです。さらに、途中で料理のコツや秘訣もご紹介します。🎉 最後まで視聴ありがとうございます!「いいね」とチャンネル登録もお忘れなく!🙏 #TunaTransformation #CookingWithTuna #SimpleMeals #HomeCooking",
    usage: { output_tokens: 151 }

3.3 - 取得したテキストデータをResult.tsxでリッチに描画する

受け取った文字列をResultコンポーネントの中で表示します。読みやすいプレゼンテーションにするため、取得した文字列に含まれる改行( "\n" )ごとに配列でスプリット(分割分割)して、テキスト行ごとに個別の <p> 要素として描画しています。

Result.tsx (54 - 56行目)

{result.data.split("\n").map((paragraph:string, index:number) => (
               <p key={index}>{paragraph}<



まとめ

Twelve Labsの /analyze APIを使えば、動画コンテンツの持つ可能性を最大限に引き出すことがかつてないほど簡単になります。この強力なテクノロジーを使用することで、Instagram、X、ブログ用の記事など、各プラットフォーム向けに動画を魅力的なソーシャルメディア投稿へと簡単に変換できます。ソーシャルメディアで視聴者を惹きつけたい場合でも、魅力的なブログコンテンツを作成したい場合でも、その可能性は無限大です。

このアプリケーションは、Twelve LabsのAnalyze APIを活用して私が開発してきた一連のツール群の集大成でもあります。「YouTubeビデオを要約する」機能で他のインフルエンサーからインスピレーションを得たり、「ビデオ用タイトル・ハッシュタグ自動生成」ツールを通してシームレスにタイトルやタグを生成したり、そして今回の、あらゆるソーシャルメディアにコンテンツを再利用できる投稿作成ツールに至るまで、すべての開発はコンテンツ制作プロセスの効率化を目指したものです。ぜひ、これら3つの作品すべての効果的な連携体験をお楽しみください!

次のステップ

「ワンソース、マルチユース(1つのソースを多用途に活用する)」という概念をご存知ですか?コンテンツクリエイターやインフルエンサーにとって、異なるプラットフォーム向けにコンテンツを再利用してオーディエンスのエンゲージメントを最大化することは、非常に重要な戦略です。

この「ビデオのソーシャル投稿生成(Generate Social Posts for your Video)」ツールは、ビデオコンテンツクリエイターが、絵文字を多用した楽しいInstagram投稿であれ、詳細なブログ記事であれ、動画をテキストベースの形式に変換するのをサポートするために特別に設計されています。 

使い方はとても簡単です。アプリケーションに動画をアップロードし、投稿を作成したいソーシャルメディアプラットフォームのいずれかを選択するだけです。または、「箇条書きで要約を執筆する」といった独自の指示(プロンプト)や要件をカスタマイズして、希望するテキスト形式を指定することもできます。数回クリックするだけで、変換されたコンテンツの準備が整います。 

それでは、アプリ構築のステップバイステップのプロセスを始めていきましょう!


前提条件

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

  • このアプリに必要なすべてのファイルが含まれるリポジトリは、Githubで公開されています。

  • (推奨)JavaScript、TypeScript、Node、React、およびReact Queryに関する基本的な知識。


アプリの構成について

__wf_reserved_inherit

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

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

  • VideoUrlUploadForm: ビデオファイルをアップロードし、TwelveLabs APIを使ってインデックスを作成するためのシンプルなフォームを提供します。インデックス作成中のビデオを表示し、処理が完了するまでリアルタイムのステータス更新を提供します。

  • Video: 指定されたURLに基づいてビデオを表示します。アプリケーション全体の複数のコンポーネント内で再利用されます。

  • InputForm: 主要なソーシャルメディアプラットフォームを表すプリセットのラジオボタンと、ユーザーが動画から出力させたいテキストコンテンツの要件を指定できるテキストエリアで構成されています。 

  • Result: このコンポーネントは、Twelve Labsの「/generate」APIを活用し、ユーザーが選択したソーシャルプラットフォームや入力内容に基づいて、生成されたテキストコンテンツを表示します。

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

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

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


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

__wf_reserved_inherit

このアプリは、インデックスに最も新しくアップロードされた1本のビデオのみを処理対象とします。そのため、構築時(マウント時)に、デフォルトで指定されたインデックスの最新ビデオが表示されます。その連携プロセスは以下の通りです。

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

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

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

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

1.1 - App.tsx内の特定インデックスからすべてのビデオを取得する

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

Server.js (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.tsxon(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": "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を抽出し、GenerateSocialPosts.tsxに渡す

返されたビデオリストに基づいて、最初のビデオのIDをGenerateSocialPosts.tsxコンポーネントに送ります。

App.tsx (39 - 43行目)

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


1.3 - ビデオIDを使用して詳細を取得し、ビデオ画像URLを抽出してVideo.tsxに渡す

先述の全体ビデオ取得の手順と同様に、特定のビデオの詳細情報を取得するため、サーバーにリクエストするReact QueryフックのuseGetVideoを使用します。サーバーからTwelve Labs APIへGETリクエストを送信して詳細情報を取得します。(💡詳細はAPIドキュメントを参照 - GET Video

Server.js (75 - 96行目)

/** インデックスのビデオを取得 */

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は直接取得できませんでした。そのため、さらに深く探ってビデオ再生URLを抽出するために、ここで個別のGET Videoリクエストを実行しています。

{
	"_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を利用してロードして描画を行います。 

GenerateSocialPosts.tsx (110 - 116行目)

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

2 - ローカルにあるビデオファイルを使用したアップロードとインデックス作成

__wf_reserved_inherit

このアプリでは、ユーザーはローカルデバイスからビデオファイルを選択し、アップロードしてインデックス作成を実行できます。このアプリの想定ユーザーであるビデオコンテンツクリエイターが、手元のPCなどのローカル端末に完成した動画データを持っていることを想定しています。 

ビデオインデックス作成リクエスト(ここでは「タスク」と呼びます)を一度送信すると、処理の進行状況を取得し画面に表示できます。また、インデックスの進行中にもビデオを確認して視聴できるよう設定しました。なお、インデックス処理が始まって直後に再生URLが用意されるわけではなく、進捗が半分ほど進んだ段階で再生可能になります。 

  1. VideoUrlUploadForm.tsxにて、ローカルビデオファイルからインデックスタスクを生成する(POST Task

  2. Task.tsxにて、生成したタスクの進捗状況を受信し、ユーザーに表示する(GET Task

ステップごとに見てみましょう。

2.1 - VideoUrlUploadForm.tsxにてビデオファイルを使用したインデックスタスクを作る

ユーザーがローカル環境からビデオファイルを選択してフォームを送信すると、インデックス作成プロセスを開始します。indexYouTubeVideoが必要なリクエストデータ(言語、インデックスID、ビデオファイルなど)を形成して、サーバーへPOSTリクエストを送信します。その後、サーバーはTwelve Labs APIの「/tasks」エンドポイントへPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照 - POST Task

server.js (119 - 158行目)

/** ビデオのインデックスを作成し、タスクIDを返す */

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


   // Append data from 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 Video";
     return next({ status, message });
   }
 }
);

これにより、新規作成されたビデオインデックスタスクのIDが返されます。 

{
	"_id": "65e9f732bb29f13bdd6f305a"
}

2.2 - Task.tsxにてインデックスタスクの進捗を取得して表示する

前章で取得したタスクIDを元に進行状態の詳細を追い、UIを定期的にアップデートします。そのため、 taskIdが存在する際にはVideoFileUploadForm内で、このTaskコンポーネントを描画させるようにします。

VideoFileUploadForm.tsx (153行目)

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

Taskコンポーネントの中で、タスク情報をロードするために、サーバーへ通信するReact QueryフックであるuseGetTaskを使います。サーバー側の実装からTwelve Labs APIに問い合わせを投げます。(💡詳細はAPIドキュメントを参照 - GET Task

server.js (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.tsxon(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ミリ秒ごと(5秒ごと)に一度データを再取得し、リアルタイムで完了プロセスをレンダリングできるようにしています。下記のように、useQueryのrefetchIntervalオプションを活用しています。 

apiHooks.tsx (73 - 94行目)

export function useGetTask(taskId: string) {
 return useQuery<Task, Error, Task, [string, string]>({
   queryKey: [keys.TASK, taskId],
   queryFn: async (): Promise<Task> => {
     try {
       const response = await apiConfig.SERVER.get(`${apiConfig.TASKS_URL}/${taskId}`);
       const respData: Task = response.data;
       return respData;
     } catch (error) {
       console.error("Error fetching task:", error);
       throw error;
     }
   },
   refetchInterval: (query) => {
     const data = query.state.data;
     return data && (data.status === "ready" || data.status === "failed")
       ? false
       : 5000;
   },
   refetchIntervalInBackground: true,
 });
}

3 - ユーザーインプットを受け取り、記事コンテンツを生成・表示する

__wf_reserved_inherit

いよいよ一番面白く、コアな部分、すなわちビデオに基づいて任意のテキストを生成するブロックです!ユーザーインプットを受け取った後、Twelve Labs APIの「/analyze」エンドポイントを使って自由形式(オープンエンド)のテキストを生成させます。

  1. 主要なソーシャルメディアのプリセットのラジオボタンか、自由に入力できるテキストエリアからInputForm.tsxでユーザー入力を受け取る

  2. 入力に基づいて、Result.tsx内の「analyze」 APIを実行する(POST Open-ended texts

  3. Result.tsxコンポーネントの中で実行結果を出力する

詳しく各ステップを掘り下げていきます。

3.1 - Radio preset または textareaフォームより、入力を保持する

InputFormは、代表的なソーシャルプラットフォーム(Instagram、Facebook、X、blog、およびその他の要件カスタマイズ用の「Others」オプション)を処理する5つのラジオボタンで構成されたシンプルなUIです。

一般的なSNSのプリセットボタンを選んだ場合は、背景で調整された最適なシステム指令プロンプト(SNSごとに適切な文脈、形式指定など)がセットされ、result.tsx の APIに送信されます。「Others」を選択した場合は、自身でフォーカス設定したいお好みの要件を自由に設定できます。 

InputForm.tsx (76 - 107行目)

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
   event.preventDefault();
   let promptValue = "";
   let platformValue = "";


   if (instagramRef.current?.checked) {
     promptValue = "write a Instagram post with emojis, 100 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Instagram";
   } else if (facebookRef.current?.checked) {
     promptValue = "write a Facebook post with emojis, 150 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Facebook";
   } else if (xRef.current?.checked) {
     promptValue = "write a X (formerly Twitter) post with emojis, 50 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "X";
   } else if (blogRef.current?.checked) {
     promptValue = "write a blog post with details. Divide sections with subtitles. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Blog";
   } else if (textRadioRef.current?.checked) {
     const inputValue = textAreaRef.current?.value.trim();
     if (inputValue?.length && inputValue?.length > 0) {
       promptValue = inputValue;
       platformValue = `"${inputValue}"`;
     } else {
       setShowCheckWarning(true)



💡 プロンプトエンジニアリングのヒント

InstagramやFacebook、その他向けに提供している内部プロンプト定義をご覧ください。さらに詳しく知りたい方は、Twelve Labsにチューニングされたプロンプト最適化の公式ガイドも合わせて参照してください。

3.2 - 受信情報に基づいて「/analyze」APIをResult.tsxから発火する

フォームが送信され、ビデオIDおよび指示(プロンプト)が確定されると、Result.tsxからuseAnalyze などのフックが呼び出されます。その後、十二分に整えられたパラメーターで、バックエンド側からTwelve Labsの「/analyze」APIにPOSTリクエストを送ります。(💡詳細はAPIドキュメントを参照 - POST Open-ended analysis

server.tsx (98 - 116行目)

/** 動画の自由形式テキストを生成 */
app.post("/videos/:videoId/analyze", async (request, response, next) => {
 const videoId = request.params.videoId;
 let prompt = request.body.data;
 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/analyze`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...prompt, video_id: videoId, temperature: 0.3, stream: false },
   };
   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 Text";
   return next({ status, message });
 }
});


リクエストが成功すると、提供したプロンプトをベースに自動生成されたテキストを含む結果データ( data 属性)がオブジェクト形式で返されます。以下は「Instagram」を指定したときの出力データの一例です。

	id: '2403323b-b648-48a7-8897-95258e68e8a4',
    data: "👨‍🍳 ツナは猫の餌のためだけじゃない!🐱 今日は缶詰のツナを4つのおいしい料理にアレンジしてみました:🍚 野菜とスクランブルエッグがのったピリ辛ツナチャーハン、🍔 フレーバーたっぷりでルッコラをトッピングしたツナバーガー、🍝 ケッパーとレモンの皮を和えたツナパスタ、そして🍙 美味しいおやつの定番・ツナマヨおにぎり!🍲 どの料理もシンプルで栄養満点、どんな食事にもぴったりです。さらに、途中で料理のコツや秘訣もご紹介します。🎉 最後まで視聴ありがとうございます!「いいね」とチャンネル登録もお忘れなく!🙏 #TunaTransformation #CookingWithTuna #SimpleMeals #HomeCooking",
    usage: { output_tokens: 151 }

3.3 - 取得したテキストデータをResult.tsxでリッチに描画する

受け取った文字列をResultコンポーネントの中で表示します。読みやすいプレゼンテーションにするため、取得した文字列に含まれる改行( "\n" )ごとに配列でスプリット(分割分割)して、テキスト行ごとに個別の <p> 要素として描画しています。

Result.tsx (54 - 56行目)

{result.data.split("\n").map((paragraph:string, index:number) => (
               <p key={index}>{paragraph}<



まとめ

Twelve Labsの /analyze APIを使えば、動画コンテンツの持つ可能性を最大限に引き出すことがかつてないほど簡単になります。この強力なテクノロジーを使用することで、Instagram、X、ブログ用の記事など、各プラットフォーム向けに動画を魅力的なソーシャルメディア投稿へと簡単に変換できます。ソーシャルメディアで視聴者を惹きつけたい場合でも、魅力的なブログコンテンツを作成したい場合でも、その可能性は無限大です。

このアプリケーションは、Twelve LabsのAnalyze APIを活用して私が開発してきた一連のツール群の集大成でもあります。「YouTubeビデオを要約する」機能で他のインフルエンサーからインスピレーションを得たり、「ビデオ用タイトル・ハッシュタグ自動生成」ツールを通してシームレスにタイトルやタグを生成したり、そして今回の、あらゆるソーシャルメディアにコンテンツを再利用できる投稿作成ツールに至るまで、すべての開発はコンテンツ制作プロセスの効率化を目指したものです。ぜひ、これら3つの作品すべての効果的な連携体験をお楽しみください!

次のステップ

「ワンソース、マルチユース(1つのソースを多用途に活用する)」という概念をご存知ですか?コンテンツクリエイターやインフルエンサーにとって、異なるプラットフォーム向けにコンテンツを再利用してオーディエンスのエンゲージメントを最大化することは、非常に重要な戦略です。

この「ビデオのソーシャル投稿生成(Generate Social Posts for your Video)」ツールは、ビデオコンテンツクリエイターが、絵文字を多用した楽しいInstagram投稿であれ、詳細なブログ記事であれ、動画をテキストベースの形式に変換するのをサポートするために特別に設計されています。 

使い方はとても簡単です。アプリケーションに動画をアップロードし、投稿を作成したいソーシャルメディアプラットフォームのいずれかを選択するだけです。または、「箇条書きで要約を執筆する」といった独自の指示(プロンプト)や要件をカスタマイズして、希望するテキスト形式を指定することもできます。数回クリックするだけで、変換されたコンテンツの準備が整います。 

それでは、アプリ構築のステップバイステップのプロセスを始めていきましょう!


前提条件

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

  • このアプリに必要なすべてのファイルが含まれるリポジトリは、Githubで公開されています。

  • (推奨)JavaScript、TypeScript、Node、React、およびReact Queryに関する基本的な知識。


アプリの構成について

__wf_reserved_inherit

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

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

  • VideoUrlUploadForm: ビデオファイルをアップロードし、TwelveLabs APIを使ってインデックスを作成するためのシンプルなフォームを提供します。インデックス作成中のビデオを表示し、処理が完了するまでリアルタイムのステータス更新を提供します。

  • Video: 指定されたURLに基づいてビデオを表示します。アプリケーション全体の複数のコンポーネント内で再利用されます。

  • InputForm: 主要なソーシャルメディアプラットフォームを表すプリセットのラジオボタンと、ユーザーが動画から出力させたいテキストコンテンツの要件を指定できるテキストエリアで構成されています。 

  • Result: このコンポーネントは、Twelve Labsの「/generate」APIを活用し、ユーザーが選択したソーシャルプラットフォームや入力内容に基づいて、生成されたテキストコンテンツを表示します。

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

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

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


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

__wf_reserved_inherit

このアプリは、インデックスに最も新しくアップロードされた1本のビデオのみを処理対象とします。そのため、構築時(マウント時)に、デフォルトで指定されたインデックスの最新ビデオが表示されます。その連携プロセスは以下の通りです。

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

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

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

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

1.1 - App.tsx内の特定インデックスからすべてのビデオを取得する

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

Server.js (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.tsxon(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": "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を抽出し、GenerateSocialPosts.tsxに渡す

返されたビデオリストに基づいて、最初のビデオのIDをGenerateSocialPosts.tsxコンポーネントに送ります。

App.tsx (39 - 43行目)

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


1.3 - ビデオIDを使用して詳細を取得し、ビデオ画像URLを抽出してVideo.tsxに渡す

先述の全体ビデオ取得の手順と同様に、特定のビデオの詳細情報を取得するため、サーバーにリクエストするReact QueryフックのuseGetVideoを使用します。サーバーからTwelve Labs APIへGETリクエストを送信して詳細情報を取得します。(💡詳細はAPIドキュメントを参照 - GET Video

Server.js (75 - 96行目)

/** インデックスのビデオを取得 */

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は直接取得できませんでした。そのため、さらに深く探ってビデオ再生URLを抽出するために、ここで個別のGET Videoリクエストを実行しています。

{
	"_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を利用してロードして描画を行います。 

GenerateSocialPosts.tsx (110 - 116行目)

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

2 - ローカルにあるビデオファイルを使用したアップロードとインデックス作成

__wf_reserved_inherit

このアプリでは、ユーザーはローカルデバイスからビデオファイルを選択し、アップロードしてインデックス作成を実行できます。このアプリの想定ユーザーであるビデオコンテンツクリエイターが、手元のPCなどのローカル端末に完成した動画データを持っていることを想定しています。 

ビデオインデックス作成リクエスト(ここでは「タスク」と呼びます)を一度送信すると、処理の進行状況を取得し画面に表示できます。また、インデックスの進行中にもビデオを確認して視聴できるよう設定しました。なお、インデックス処理が始まって直後に再生URLが用意されるわけではなく、進捗が半分ほど進んだ段階で再生可能になります。 

  1. VideoUrlUploadForm.tsxにて、ローカルビデオファイルからインデックスタスクを生成する(POST Task

  2. Task.tsxにて、生成したタスクの進捗状況を受信し、ユーザーに表示する(GET Task

ステップごとに見てみましょう。

2.1 - VideoUrlUploadForm.tsxにてビデオファイルを使用したインデックスタスクを作る

ユーザーがローカル環境からビデオファイルを選択してフォームを送信すると、インデックス作成プロセスを開始します。indexYouTubeVideoが必要なリクエストデータ(言語、インデックスID、ビデオファイルなど)を形成して、サーバーへPOSTリクエストを送信します。その後、サーバーはTwelve Labs APIの「/tasks」エンドポイントへPOSTリクエストを送信します。(💡詳細はAPIドキュメントを参照 - POST Task

server.js (119 - 158行目)

/** ビデオのインデックスを作成し、タスクIDを返す */

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


   // Append data from 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 Video";
     return next({ status, message });
   }
 }
);

これにより、新規作成されたビデオインデックスタスクのIDが返されます。 

{
	"_id": "65e9f732bb29f13bdd6f305a"
}

2.2 - Task.tsxにてインデックスタスクの進捗を取得して表示する

前章で取得したタスクIDを元に進行状態の詳細を追い、UIを定期的にアップデートします。そのため、 taskIdが存在する際にはVideoFileUploadForm内で、このTaskコンポーネントを描画させるようにします。

VideoFileUploadForm.tsx (153行目)

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

Taskコンポーネントの中で、タスク情報をロードするために、サーバーへ通信するReact QueryフックであるuseGetTaskを使います。サーバー側の実装からTwelve Labs APIに問い合わせを投げます。(💡詳細はAPIドキュメントを参照 - GET Task

server.js (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.tsxon(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ミリ秒ごと(5秒ごと)に一度データを再取得し、リアルタイムで完了プロセスをレンダリングできるようにしています。下記のように、useQueryのrefetchIntervalオプションを活用しています。 

apiHooks.tsx (73 - 94行目)

export function useGetTask(taskId: string) {
 return useQuery<Task, Error, Task, [string, string]>({
   queryKey: [keys.TASK, taskId],
   queryFn: async (): Promise<Task> => {
     try {
       const response = await apiConfig.SERVER.get(`${apiConfig.TASKS_URL}/${taskId}`);
       const respData: Task = response.data;
       return respData;
     } catch (error) {
       console.error("Error fetching task:", error);
       throw error;
     }
   },
   refetchInterval: (query) => {
     const data = query.state.data;
     return data && (data.status === "ready" || data.status === "failed")
       ? false
       : 5000;
   },
   refetchIntervalInBackground: true,
 });
}

3 - ユーザーインプットを受け取り、記事コンテンツを生成・表示する

__wf_reserved_inherit

いよいよ一番面白く、コアな部分、すなわちビデオに基づいて任意のテキストを生成するブロックです!ユーザーインプットを受け取った後、Twelve Labs APIの「/analyze」エンドポイントを使って自由形式(オープンエンド)のテキストを生成させます。

  1. 主要なソーシャルメディアのプリセットのラジオボタンか、自由に入力できるテキストエリアからInputForm.tsxでユーザー入力を受け取る

  2. 入力に基づいて、Result.tsx内の「analyze」 APIを実行する(POST Open-ended texts

  3. Result.tsxコンポーネントの中で実行結果を出力する

詳しく各ステップを掘り下げていきます。

3.1 - Radio preset または textareaフォームより、入力を保持する

InputFormは、代表的なソーシャルプラットフォーム(Instagram、Facebook、X、blog、およびその他の要件カスタマイズ用の「Others」オプション)を処理する5つのラジオボタンで構成されたシンプルなUIです。

一般的なSNSのプリセットボタンを選んだ場合は、背景で調整された最適なシステム指令プロンプト(SNSごとに適切な文脈、形式指定など)がセットされ、result.tsx の APIに送信されます。「Others」を選択した場合は、自身でフォーカス設定したいお好みの要件を自由に設定できます。 

InputForm.tsx (76 - 107行目)

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
   event.preventDefault();
   let promptValue = "";
   let platformValue = "";


   if (instagramRef.current?.checked) {
     promptValue = "write a Instagram post with emojis, 100 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Instagram";
   } else if (facebookRef.current?.checked) {
     promptValue = "write a Facebook post with emojis, 150 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Facebook";
   } else if (xRef.current?.checked) {
     promptValue = "write a X (formerly Twitter) post with emojis, 50 words or less. Do not provide an explanation. Do not provide a summary.";
     platformValue = "X";
   } else if (blogRef.current?.checked) {
     promptValue = "write a blog post with details. Divide sections with subtitles. Do not provide an explanation. Do not provide a summary.";
     platformValue = "Blog";
   } else if (textRadioRef.current?.checked) {
     const inputValue = textAreaRef.current?.value.trim();
     if (inputValue?.length && inputValue?.length > 0) {
       promptValue = inputValue;
       platformValue = `"${inputValue}"`;
     } else {
       setShowCheckWarning(true)



💡 プロンプトエンジニアリングのヒント

InstagramやFacebook、その他向けに提供している内部プロンプト定義をご覧ください。さらに詳しく知りたい方は、Twelve Labsにチューニングされたプロンプト最適化の公式ガイドも合わせて参照してください。

3.2 - 受信情報に基づいて「/analyze」APIをResult.tsxから発火する

フォームが送信され、ビデオIDおよび指示(プロンプト)が確定されると、Result.tsxからuseAnalyze などのフックが呼び出されます。その後、十二分に整えられたパラメーターで、バックエンド側からTwelve Labsの「/analyze」APIにPOSTリクエストを送ります。(💡詳細はAPIドキュメントを参照 - POST Open-ended analysis

server.tsx (98 - 116行目)

/** 動画の自由形式テキストを生成 */
app.post("/videos/:videoId/analyze", async (request, response, next) => {
 const videoId = request.params.videoId;
 let prompt = request.body.data;
 try {
   const options = {
     method: "POST",
     url: `${API_BASE_URL}/analyze`,
     headers: { ...HEADERS, accept: "application/json" },
     data: { ...prompt, video_id: videoId, temperature: 0.3, stream: false },
   };
   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 Text";
   return next({ status, message });
 }
});


リクエストが成功すると、提供したプロンプトをベースに自動生成されたテキストを含む結果データ( data 属性)がオブジェクト形式で返されます。以下は「Instagram」を指定したときの出力データの一例です。

	id: '2403323b-b648-48a7-8897-95258e68e8a4',
    data: "👨‍🍳 ツナは猫の餌のためだけじゃない!🐱 今日は缶詰のツナを4つのおいしい料理にアレンジしてみました:🍚 野菜とスクランブルエッグがのったピリ辛ツナチャーハン、🍔 フレーバーたっぷりでルッコラをトッピングしたツナバーガー、🍝 ケッパーとレモンの皮を和えたツナパスタ、そして🍙 美味しいおやつの定番・ツナマヨおにぎり!🍲 どの料理もシンプルで栄養満点、どんな食事にもぴったりです。さらに、途中で料理のコツや秘訣もご紹介します。🎉 最後まで視聴ありがとうございます!「いいね」とチャンネル登録もお忘れなく!🙏 #TunaTransformation #CookingWithTuna #SimpleMeals #HomeCooking",
    usage: { output_tokens: 151 }

3.3 - 取得したテキストデータをResult.tsxでリッチに描画する

受け取った文字列をResultコンポーネントの中で表示します。読みやすいプレゼンテーションにするため、取得した文字列に含まれる改行( "\n" )ごとに配列でスプリット(分割分割)して、テキスト行ごとに個別の <p> 要素として描画しています。

Result.tsx (54 - 56行目)

{result.data.split("\n").map((paragraph:string, index:number) => (
               <p key={index}>{paragraph}<



まとめ

Twelve Labsの /analyze APIを使えば、動画コンテンツの持つ可能性を最大限に引き出すことがかつてないほど簡単になります。この強力なテクノロジーを使用することで、Instagram、X、ブログ用の記事など、各プラットフォーム向けに動画を魅力的なソーシャルメディア投稿へと簡単に変換できます。ソーシャルメディアで視聴者を惹きつけたい場合でも、魅力的なブログコンテンツを作成したい場合でも、その可能性は無限大です。

このアプリケーションは、Twelve LabsのAnalyze APIを活用して私が開発してきた一連のツール群の集大成でもあります。「YouTubeビデオを要約する」機能で他のインフルエンサーからインスピレーションを得たり、「ビデオ用タイトル・ハッシュタグ自動生成」ツールを通してシームレスにタイトルやタグを生成したり、そして今回の、あらゆるソーシャルメディアにコンテンツを再利用できる投稿作成ツールに至るまで、すべての開発はコンテンツ制作プロセスの効率化を目指したものです。ぜひ、これら3つの作品すべての効果的な連携体験をお楽しみください!

次のステップ