チュートリアル

Twelve Labs を使ったオリンピック動画分類アプリケーションの構築

リシケシュ・ヤダフ

このチュートリアルでは、Twelve Labsの分類APIとMarengo 2.6を使用し、モデルのトレーニングを一切行うことなく、Streamlitインターフェースを介してスポーツ映像を定義済みまたはカスタムのクラスに自動分類する「オリンピック・ビデオクリップ分類アプリケーション」の構築手順を説明します。

このチュートリアルでは、Twelve Labsの分類APIとMarengo 2.6を使用し、モデルのトレーニングを一切行うことなく、Streamlitインターフェースを介してスポーツ映像を定義済みまたはカスタムのクラスに自動分類する「オリンピック・ビデオクリップ分類アプリケーション」の構築手順を説明します。

この記事の内容

No headings found on page

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

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

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

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

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

2024/09/03

15分

記事へのリンクをコピー

オリンピックのビデオ映像を整理してメダルを獲得することを夢見たことはありませんか? 🥇 さあ、表彰台に立つチャンスです!

オリンピック動画クリップ分類アプリケーションは、スポーツ映像をカテゴリ分けする退屈なプロセスを効率化することを目指しています。Twelve LabsのMarengo 2.6埋め込みモデルを使用し、このアプリは動画クリップからオリンピック競技を迅速に分類します。

画面上のテキスト、会話、視覚情報を分析することにより、アプリケーションは動画クリップを簡単にカテゴリ分けします。このチュートリアルでは、研究者、スポーツ愛好家、放送局がオリンピックコンテンツと対話する方法に革命をもたらすStreamlitアプリケーションの作成方法をガイドします。ユーザー定義のカテゴリに基づいて動画を分類するアプリの構築方法を学びます。アプリケーションの詳細なデモンストレーション動画は、以下に提供されています。

アプリケーションのデモはこちらでご覧いただけます:Olympics Application。また、こちらのReplitテンプレートを使って触ってみることもできます。

前提条件

  • Twelve Labs Playgroundにサインアップして、APIキーを生成します。

  • ノートブックとこのアプリケーションのリポジトリはGitHubにあります。

  • Streamlitアプリケーションは、Python、HTML、JavaScriptを使用しています。



アプリケーションの仕組み

本セクションでは、オリンピック動画クリップを分類するためのアプリケーションフローの概要を説明します。

アプリケーションは分類検索エンジンを使用しており、さまざまな潜在的用途があります。このタスクでは、オリンピックのスポーツ動画クリップの分類と取得に焦点を当てます。最初の重要なステップは、インデックスの作成です。

  • これを行うには、Twelve Labs Playgroundにアクセスしてください。

  • [New Index (新しいインデックス)] を作成し、インデックス作成と分類に適した Marengo 2.6 (Embedding Engine) を選択します。

  • すべてのスポーツ動画がこのインデックスにアップロードされたら、次に進むことができます。

次のセクションでは、インデックスIDを作成して実装する方法をステップバイステップで詳しく説明します。一般的なアプリケーションワークフローの概要は以下のとおりです:

  • ユーザーには2つのオプションがあります。マルチセレクトメニューから表示したいスポーツのタイプを選択するか、独自のカスタムクラスを追加することができます。

  • 選択されたクラスは、関連するすべての動画クリップを取得および分類するために、 INDEX ID と共にclassify(分類)エンドポイントに送信されます。 include_clips などの追加パラメータもこのエンドポイントに送信されます。

  • レスポンスには、ビデオID、クラス、信頼度(信頼スコア)、開始および終了時間、サムネイルURLなどが含まれます。

  • 結果のビデオIDに関連付けられたビデオURLを取得するには、ビデオ情報エンドポイントにアクセスする必要があります。

準備ステップ

  • Twelve Labs Playgroundでサインアップし、インデックスを作成します。

  • 分類に適切なオプションを選択します:Marengo 2.6 (Embedding Engine)。このエンジンは動画検索と分類に優れており、動画理解のための堅牢な基盤を提供します。

  • スポーツ動画クリップをアップロードします。必要に応じて、このチュートリアル用に提供されているサンプルクリップを使用してください:Sports Clips

  • Twelve Labs PlaygroundからAPIキーを取得します。

  • オリンピックのクリップが含まれているインデックスを開いて、 INDEX_ID を取得します。IDはURLに含まれています:https://playground.twelvelabs.io/indexes/{index_id}

  • メインファイルと共に、APIキーとINDEX_IDを設定した .env ファイルをセットアップします。

これらのステップが完了すれば、アプリケーションの開発に取り掛かる準備は完了です!

Twelvelabs_API=your_api_key_here
API_URL=your_api_url_here
INDEX_ID=your_index_id_here

分類のウォークスルー

1 - クライアントのインポート、検証、セットアップ

まず、必要なライブラリをインポートし、正しい環境を作成するために環境変数を設定することから始めます。開始するには:

import os
import requests
import glob
import requests
from twelvelabs import TwelveLabs
from twelvelabs.models.task import Task
import dotenv


API_KEY=os.getenv("Twelvelabs_API")
API_URL=os.getenv("API_URL")
INDEX_ID=os.getenv("INDEX_ID")

client = TwelveLabs(api_key=API_KEY)

# URL of the /indexes endpoint
INDEXES_URL = f"{API_URL}/indexes"
     
# Setting headers variables

default_header = {
    "x-api-key": API_KEY
}

依存関係がインポートされ、環境変数が正しくロードされ、適切なシークレット情報でAPIクライアントが初期化されました。インデックスにアクセスするためのベースURLと、APIリクエストのデフォルトヘッダーも設定されています。

この基盤が整ったので、インデックスを作成するなど、より具体的な分類タスクを進める準備が整いました。

2 - クラスの設定

以下は、オリンピックスポーツのクリップを分類するためのクラスのリストです:

# Categories for the classification of the Olympics Sports

CLASSES = [
    {
        "name": "AquaticSports",
        "prompts": [
            "swimming competition",
            "diving event",
            "water polo match",
            "synchronized swimming",
            "open water swimming"
        ]
    },
    {
        "name": "AthleticEvents",
        "prompts": [
            "track and field",
            "marathon running",
            "long jump competition",
            "javelin throw",
            "high jump event"
        ]
    },
    {
        "name": "GymnasticsEvents",
        "prompts": [
            "artistic gymnastics",
            "rhythmic gymnastics",
            "trampoline gymnastics",
            "balance beam routine",
            "floor exercise performance"
        ]
    },
    {
        "name": "CombatSports",
        "prompts": [
            "boxing match",
            "judo competition",
            "wrestling bout",
            "taekwondo fight",
            "fencing duel"
        ]
    },
    {
        "name": "TeamSports",
        "prompts": [
            "basketball game",
            "volleyball match",
            "football (soccer) match",
            "handball game",
            "field hockey competition"
        ]
    },
    {
        "name": "CyclingSports",
        "prompts": [
            "road cycling race",
            "track cycling event",
            "mountain bike competition",
            "BMX racing",
            "cycling time trial"
        ]
    },
    {
        "name": "RacquetSports",
        "prompts": [
            "tennis match",
            "badminton game",
            "table tennis competition",
            "squash game",
            "tennis doubles match"
        ]
    },
    {
        "name": "RowingAndSailing",
        "prompts": [
            "rowing competition",
            "sailing race",
            "canoe sprint",
            "kayak event",
            "windsurfing competition"
        ]
    }
]

3 - 特定のインデックスからすべての動画を分類するためのユーティリティ関数

# Utility function
def print_page(page):
    for data in page:
        print(f"video_id={data.video_id}")
        for cl in data.classes:
            print(
                f"  name={cl.name} score={cl.score} duration_ratio={cl.duration_ratio} clips={cl.clips.model_dump_json(indent=2)}"
            )


result = client.classify.index(
    index_id=INDEX_ID,
    options=["visual"],
    classes=CLASSES,
    include_clips=True
)

分類を実行するために、 client.classify.index() メソッドが呼び出されます。これは以前に定義された INDEX_ID を使用し、視覚的な分類(options=["visual"])に焦点を当て、結果にクリップ情報を含めます。 CLASSES パラメータは、分類に使用するクラスを指定します。

print_page(result) 関数は、分類結果を読みやすい形式で表示します。これは分類レスポンスを反復処理し、ビデオIDと、名前、スコア、デュレーション比(時間比率)、関連クリップなど、分類された各クラスに関する詳細情報を出力します。このアプローチにより、指定したインデックス内のすべてのビデオにわたる分類出力を簡単に分析できます。

4 - 結果を構造化して明確に出力する関数

print_classification_result() 関数は、TwelveLabs APIの分類結果を構造化された読みやすい形式で表示します。結果セット内の各動画を処理し、ビデオID、検出されたクラス、関連するスコアなどの重要情報を提示します。クラスごとに、全体的なスコアとデュレーション比を表示し、続いてスコアの降順でソートされた上位5つのクリップの詳細を表示します。これらのクリップ詳細には、開始時間と終了時間、および CLASSES 内の関連プロンプトが含まれます。この関数は、上位5つ以外に追加のクリップがあるかどうかも示します。

def print_classification_result(result) -> None:

    for video_data in result.data:
        print(f"Video ID: {video_data.video_id}")
        print("=" * 50)


        for class_data in video_data.classes:
            print(f"  Class: {class_data.name}")
            print(f"  Score: {class_data.score:.2f}")
            print(f"  Duration Ratio: {class_data.duration_ratio:.2f}")
            print("  Clips:")


            sorted_clips = sorted(class_data.clips, key=lambda x: x.score, reverse=True)


            for i, clip in enumerate(sorted_clips[:5], 1):  # Print top 5 clips
                print(f"    {i}. Score: {clip.score:.2f}")
                print(f"       Start: {clip.start:.2f}s, End: {clip.end:.2f}s")
                print(f"       Prompt: {clip.prompt}")


            if len(sorted_clips) > 5:
                print(f"    ... and {len(sorted_clips) - 5} more clips")


            print("-" * 40)


        print("\n")


    print(f"Total results: {result.page_info.total_results}")
    print(f"Page expires at: {result.page_info.page_expired_at}")
    print(f"Next page token: {result.page_info.next_page_token}")



上記の結果のサンプル出力は以下のようになります -

Video ID: 66c9b03be53394f4aaed82c1
==================================================
  Class: AquaticSports
  Score: 96.08
  Duration Ratio: 0.90
  Clips:
    1. Score: 85.74
       Start: 19.30s, End: 42.00s
       Prompt: water polo match
    2. Score: 85.38
       Start: 56.30s, End: 160.41s
       Prompt: water polo match
    3. Score: 85.25
       Start: 19.30s, End: 124.07s
       Prompt: synchronized swimming
    4. Score: 85.13
       Start: 0.00s, End: 24.83s
       Prompt: swimming competition
    5. Score: 85.08
       Start: 124.10s, End: 160.41s
       Prompt: synchronized swimming
    ... and 19 more clips

5 - インデックス内のすべての動画からの特定クラスのカテゴリ分け

このセクションでは、特定のインデックス内のすべての動画に対する「AquaticSports」の特定クラスのカテゴリ分けに焦点を当てます。このプロセスは、関連するプロンプトを持つ特定クラスの定義、分類の実行、および結果の表示という3つの主要なステップで構成されます。

この絞り込んだアプローチにより、水上スポーツのコンテンツを含む動画をより正確に分類できるようになります。 print_page() 関数はユーティリティツールとして機能し、分類結果を分かりやすく出力します。

CLASS = [
    {
        "name": "AquaticSports",
        "prompts": [
            "swimming competition",
            "diving event",
            "water polo match",
            "synchronized swimming",
            "open water swimming"
        ]
    }


]


def print_page(page):
    for data in page:
        print(f"video_id={data.video_id}")
        for cl in data.classes:
            print(
                f"  name={cl.name} score={cl.score} duration_ratio={cl.duration_ratio} detailed_scores={cl.detailed_scores.model_dump_json(indent=2)}"
            )
result = client.classify.index(
    index_id=INDEX_ID,
    options=["visual"],
    classes=CLASS,
    include_clips=True,
    show_detailed_score=True
)


print_classification_result(result)

上記のコードスニペットのサンプル出力セクションは、特定の CLASS に分類される動画を提供します。

Video ID: 66c9b03be53394f4aaed82c1
==================================================
  Class: AquaticSports
  Score: 96.08
  Duration Ratio: 0.90
  Clips:
    1. Score: 85.74
       Start: 19.30s, End: 42.00s
       Prompt: water polo match
    2. Score: 85.38
       Start: 56.30s, End: 160.41s
       Prompt: water polo match
    3. Score: 85.25
       Start: 19.30s, End: 124.07s
       Prompt: synchronized swimming
    4. Score: 85.13
       Start: 0.00s, End: 24.83s
       Prompt: swimming competition
    5. Score: 85.08
       Start: 124.10s, End: 160.41s
       Prompt: synchronized swimming
    ... and 19 more clips

Twelve Labs SDKを使用してclassifyエンドポイントを実装し、それと対話する方法を理解したところで、ユーザー向けに完全なStreamlitアプリケーションの開発に進みましょう。

Streamlitアプリケーションの作成

Streamlitは、そのシンプルさと迅速なプロトタイピング機能により、開発の初期段階に最適であるため、アプリケーションの構築用として選択されました。Streamlitを使用すると、最小限のコードでインタラクティブなWebインターフェイスを迅速に作成でき、分類タスクの結果をユーザーフレンドリーな方法で紹介するのに最適です。Twelve Labs SDKを組み合わせることで、高度にインタラクティブなWebインターフェイスを数分で作成できます。

Streamlitを使い始めるには、仮想環境を設定していることを確認し、次のコマンドを使用してStreamlitをインストールします:

pip install streamlit

詳細については、公式のStreamlitドキュメントを確認してください。

Streamlitアプリケーションを構築するには、次のファイル構造を使用します:

.
├── .env
├── app.py
├── requirements.txt
└── .gitignore

requirements.txt ファイルの内容は以下のとおりです:

streamlit
twelvelabs
requests
python-dotenv

これらの依存関係は、オリンピックの動画クリップ分類アプリケーションに必要なコア機能をカバーしています。

メインの app.py ファイルには、以下の機能が含まれています:

  • get_initial_classes(): 事前定義されたオリンピックスポーツカテゴリを設定します。

  • get_custom_classes() および add_custom_class(): 柔軟性を高めるために、ユーザー定義のカテゴリを管理します。

  • classify_videos(): TwelveLabs APIと連携して、選択したカテゴリに基づいて動画の分類を実行します。

  • get_video_urls(): 分類されたコンテンツのビデオURLを取得します。

  • render_video(): スムーズな再生のために、HLS.jsを使用して組み込みビデオプレーヤーを作成します。

# Import Necessary Dependencies
import streamlit as st
from twelvelabs import TwelveLabs
import requests
import os
from dotenv import load_dotenv

load_dotenv()

# Get the API Key from the Dashboard - https://playground.twelvelabs.io/dashboard/api-key
API_KEY = os.getenv("API_KEY")

# Create the INDEX ID as specified in the README.md and get the INDEX_ID
INDEX_ID = os.getenv("INDEX_ID")

client = TwelveLabs(api_key=API_KEY)

# Background Setting of the Application
page_element = """
<style>
[data-testid="stAppViewContainer"] {
    background-image: url("https://wallpapercave.com/wp/wp3589963.jpg");
    background-size: cover;
}
[data-testid="stHeader"] {
    background-color: rgba(0,0,0,0);
}
[data-testid="stToolbar"] {
    right: 2rem;
    background-image: url("");
    background-size: cover;
}
<&sol;style>
"""
st.markdown(page_element, unsafe_allow_html=True)


# Classes to classify the video into, there are categories name and 
# the prompts which specifc finds that factor to label that category

@st.cache_data
def get_initial_classes():
    return [
        {"name": "AquaticSports", "prompts": ["swimming competition", "diving event", "water polo match", "synchronized swimming", "open water swimming"]},
        {"name": "AthleticEvents", "prompts": ["track and field", "marathon running", "long jump competition", "javelin throw", "high jump event"]},
        {"name": "GymnasticsEvents", "prompts": ["artistic gymnastics", "rhythmic gymnastics", "trampoline gymnastics", "balance beam routine", "floor exercise performance"]},
        {"name": "CombatSports", "prompts": ["boxing match", "judo competition", "wrestling bout", "taekwondo fight", "fencing duel"]},
        {"name": "TeamSports", "prompts": ["basketball game", "volleyball match", "football (soccer) match", "handball game", "field hockey competition"]},
        {"name": "CyclingSports", "prompts": ["road cycling race", "track cycling event", "mountain bike competition", "BMX racing", "cycling time trial"]},
        {"name": "RacquetSports", "prompts": ["tennis match", "badminton game", "table tennis competition", "squash game", "tennis doubles match"]},
        {"name": "RowingAndSailing", "prompts": ["rowing competition", "sailing race", "canoe sprint", "kayak event", "windsurfing competition"]}
    ]

# Session State for the custom classes 
def get_custom_classes():
    if 'custom_classes' not in st.session_state:
        st.session_state.custom_classes = []
    return st.session_state.custom_classes

# Utitlity Function to add the custom classes in app
def add_custom_class(name, prompts):
    custom_classes = get_custom_classes()
    custom_classes.append({"name": name, "prompts": prompts})
    st.session_state.custom_classes = custom_classes
    st.session_state.new_class_added = True

# Utitlity Function to classify all the videos in the specified Index
def classify_videos(selected_classes):
    return client.classify.index(
        index_id=INDEX_ID,
        options=["visual"],
        classes=selected_classes,
        include_clips=True
    )

# To get the video urls from the resultant video id
def get_video_urls(video_ids):
    base_url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos/{{}}"
    headers = {"x-api-key": API_KEY, "Content-Type": "application/json"}
    video_urls = {}

    for video_id in video_ids:
        try:
            response = requests.get(base_url.format(video_id), headers=headers)
            response.raise_for_status()
            data = response.json()
            if 'hls' in data and 'video_url' in data['hls']:
                video_urls[video_id] = data['hls']['video_url']
            else:
                st.warning(f"No video URL found for video ID: {video_id}")
        except requests.exceptions.RequestException as e:
            st.error(f"Failed to get data for video ID: {video_id}. Error: {str(e)}")

    return video_urls

# Utitlity Function to Render the Video by the resultant video url
def render_video(video_url):
    hls_player = f"""
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"><&sol;script>
    <div style="width: 100%; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
        <video id="video" controls style="width: 100%; height: auto;"><&sol;video>
    <&sol;div>
    <script>
      var video = document.getElementById('video');
      var videoSrc = "{video_url}";
      if (Hls.isSupported()) {{
        var hls = new Hls();
        hls.loadSource(videoSrc);
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, function() {{
          video.pause();
        }});
      }}
      else if (video.canPlayType('application/vnd.apple.mpegurl')) {{
        video.src = videoSrc;
        video.addEventListener('loadedmetadata', function() {{
          video.pause();
        }});
      }}
    <&sol;script>
    """
    st.components.v1.html(hls_player, height=300)

get_initial_classes() 関数は、オリンピックスポーツの事前定義されたカテゴリの静的なリストを返します。Streamlitはこのデータをキャッシュするため、関数が実行されるのは1回だけで、その結果はメモリに保存されます。この関数をそれ以降に呼び出すと、キャッシュされた結果が返されるため、繰り返しの実行を避けることができます。

get_initial_classes() 関数はキャッシュされますが、get_custom_classes() はキャッシュされません。Streamlitのセッション状態に保存されるカスタムクラスは、アプリケーションの実行中に変化することが予想されるためです。

get_video_urls() はビデオIDのリストを受け取り、TwelveLabsに対してAPIコールを行って対応するHLS (HTTP Live Streaming) URLを取得し、潜在的なエラーや欠落しているURLを処理します。

render_video() は、複数のブラウザやデバイスと互換性のある、HLS.jsサポート付きのHTML5ビデオプレーヤーを作成します。必要に応じてネイティブのHLSにフォールバックします。

classify_videos() では、TwelveLabsクライアントを使用して、指定されたインデックス内のビデオを分類するために、ユーザーが選択したクラスが使用されます。この関数は視覚的な分類に焦点を当て、結果にクリップ情報を含めます。

# Main Function
def main():
    # Basic Markdown Setup for the Application
    st.markdown("""
    <style>
    .big-font {
        font-size: 40px !important;
        font-weight: bold;
        color: #000000;
        text-align: center;
        margin-bottom: 30px;
    }
    .subheader {
        font-size: 24px;
        font-weight: bold;
        color: #424242;
        margin-top: 20px;
        margin-bottom: 10px;
    }
    .stButton>button {
        width: 100%;
    }
    .video-info {
        background-color: #f0f0f0;
        border-radius: 10px;
        padding: 15px;
        margin-bottom: 20px;
    }
    .custom-box {
        background-color: #f9f9f9;
        border-radius: 10px;
        padding: 20px;
        margin-bottom: 20px;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .stTabs [data-baseweb="tab-list"] {
        gap: 24px;
    }
    .stTabs [data-baseweb="tab"] {
        height: 50px;
        white-space: pre-wrap;
        background-color: #f0f2f6;
        border-radius: 4px 4px 0px 0px;
        gap: 1px;
        padding-top: 10px;
        padding-bottom: 10px;
    }
    .stTabs [data-baseweb="tab-list"] button[aria-selected="true"] {
        background-color: #e8eaed;
    }
    <&sol;style>
    """, unsafe_allow_html=True)

    st.markdown('<p class="big-font">Olympics Classification w/t Twelve Labs<&sol;p>', unsafe_allow_html=True)
    
    # Updation of the classes
    CLASSES = get_initial_classes() + get_custom_classes()
    
    # Nav Tabs Creation
    tab1, tab2 = st.tabs(["Select Classes", "Add Custom Class"])
    
    with tab1:
        st.markdown('<p class="subheader">Select Classes<&sol;p>', unsafe_allow_html=True)
        with st.container():

            class_names = [cls["name"] for cls in CLASSES]
            # Multiselect option from the CLASSES
            selected_classes = st.multiselect("Choose one or more Olympic sports categories:", class_names)
            if st.button("Classify Videos", key="classify_button"):
                if selected_classes:
                    with st.spinner("Classifying videos..."):
                        selected_classes_with_prompts = [cls for cls in CLASSES if cls["name"] in selected_classes]
                        res = classify_videos(selected_classes_with_prompts)
                        
                        video_ids = [data.video_id for data in res.data]
                        # Retrieving the video urls from the resultant video which matches to the selected CLASSES
                        video_urls = get_video_urls(video_ids)
                        
                        st.markdown('<p class="subheader">Classified Videos<&sol;p>', unsafe_allow_html=True)
                        
                        # Iterating over to showcase the information for every resulatant video
                        for i, video_data in enumerate(res.data, 1):
                            video_id = video_data.video_id
                            video_url = video_urls.get(video_id, "URL not found")
                            
                            st.markdown(f"### Video {i}")
                            st.markdown('<div class="video-info">', unsafe_allow_html=True)
                            st.markdown(f"**Video ID:** {video_id}")
                            
                            for class_data in video_data.classes:
                                st.markdown(f"""
                                **Class:** {class_data.name}
                                - Score: {class_data.score:.2f}
                                - Duration Ratio: {class_data.duration_ratio:.2f}
                                """)
                            
                            if video_url != "URL not found":
                                render_video(video_url)
                            else:
                                st.warning("Video URL not available. Unable to render video.")
                            
                            st.markdown("---")
                        
                        st.success(f"Total videos classified: {len(res.data)}")
                else:
                    st.warning("Please select at least one class.")
            st.markdown('<&sol;div>', unsafe_allow_html=True)

        # Nav Tab for the addition of the Custom Classes to select from
        with tab2:
            st.markdown('<p class="subheader">Add Custom Class<&sol;p>', unsafe_allow_html=True)
            with st.container():
                custom_class_name = st.text_input("Enter custom class name")
                custom_class_prompts = st.text_input("Enter custom class prompts (comma-separated)")
                if st.button("Add Custom Class"):
                    if custom_class_name and custom_class_prompts:
                        add_custom_class(custom_class_name, custom_class_prompts.split(','))
                        st.success(f"Custom class '{custom_class_name}' added successfully!")
                        st.experimental_rerun()
                    else:
                        st.warning("Please enter both class name and prompts.")
                st.markdown('<&sol;div>', unsafe_allow_html=True)

    if st.session_state.get('new_class_added', False):
        st.session_state.new_class_added = False
        st.experimental_rerun()

if __name__ == "__main__":
    main()

アプリケーションのメイン関数では、指示の正しい順序だけでなく、CSSとレイアウトに大きく重点を置いています。ユーザーは動画分類タブの下にある [Add Custom Class (カスタムクラスの追加)] タブから新しい分類カテゴリを追加できます。[Select Classes (クラスの選択)] タブを使用すると、ユーザーは定義されたカテゴリおよびカスタムカテゴリから選択できます。

ユーザーが「Classify Videos(動画の分類)」ボタンをクリックすると、アプリケーションは次の処理を行います:

  • 選択されたクラスを取得します。

  • TwelveLabs APIを呼び出して動画を分類します(classify_videos() 関数)。

  • 分類された動画の動画URLを取得します。

  • 分類結果を表示し、動画をレンダリングします。

最終的なアプリケーション結果は以下の通りです —

チュートリアルをさらに試すためのアイデア

アプリケーションの動作手順と開発プロセスを理解することで、革新的なアイデアを実装し、現実世界にインパクトを与える準備が整います。このチュートリアルブログに類似した、さらに発展させることができるいくつかのユースケースのアイデアをご紹介します:

🔍 ビデオ検索エンジン:ビデオコンテンツの検索可能なデータベースを作成し、ユーザーが大容量の動画コレクションから特定のシーンやトピックを見つけられるようにします。

🎥 防犯カメラ映像分析:防犯カメラ映像内の特定のイベントや行動を検出してカテゴリ分けします。

💃 ダンスムーブ分類:ダンス動画からさまざまなダンススタイルや特定の動きを識別し、カテゴリ分けします。

結論

このブログ記事では、Twelve Labsを活用した分類タスクを使って、アプリケーションがどのように構築されるのか、その動作手順を詳細に解説しました。チュートリアルにお付き合いいただきありがとうございました。ユーザーエクスペリエンスの向上やさまざまな問題の解決に関する皆様のアイデアを楽しみにしています。

オリンピックのビデオ映像を整理してメダルを獲得することを夢見たことはありませんか? 🥇 さあ、表彰台に立つチャンスです!

オリンピック動画クリップ分類アプリケーションは、スポーツ映像をカテゴリ分けする退屈なプロセスを効率化することを目指しています。Twelve LabsのMarengo 2.6埋め込みモデルを使用し、このアプリは動画クリップからオリンピック競技を迅速に分類します。

画面上のテキスト、会話、視覚情報を分析することにより、アプリケーションは動画クリップを簡単にカテゴリ分けします。このチュートリアルでは、研究者、スポーツ愛好家、放送局がオリンピックコンテンツと対話する方法に革命をもたらすStreamlitアプリケーションの作成方法をガイドします。ユーザー定義のカテゴリに基づいて動画を分類するアプリの構築方法を学びます。アプリケーションの詳細なデモンストレーション動画は、以下に提供されています。

アプリケーションのデモはこちらでご覧いただけます:Olympics Application。また、こちらのReplitテンプレートを使って触ってみることもできます。

前提条件

  • Twelve Labs Playgroundにサインアップして、APIキーを生成します。

  • ノートブックとこのアプリケーションのリポジトリはGitHubにあります。

  • Streamlitアプリケーションは、Python、HTML、JavaScriptを使用しています。



アプリケーションの仕組み

本セクションでは、オリンピック動画クリップを分類するためのアプリケーションフローの概要を説明します。

アプリケーションは分類検索エンジンを使用しており、さまざまな潜在的用途があります。このタスクでは、オリンピックのスポーツ動画クリップの分類と取得に焦点を当てます。最初の重要なステップは、インデックスの作成です。

  • これを行うには、Twelve Labs Playgroundにアクセスしてください。

  • [New Index (新しいインデックス)] を作成し、インデックス作成と分類に適した Marengo 2.6 (Embedding Engine) を選択します。

  • すべてのスポーツ動画がこのインデックスにアップロードされたら、次に進むことができます。

次のセクションでは、インデックスIDを作成して実装する方法をステップバイステップで詳しく説明します。一般的なアプリケーションワークフローの概要は以下のとおりです:

  • ユーザーには2つのオプションがあります。マルチセレクトメニューから表示したいスポーツのタイプを選択するか、独自のカスタムクラスを追加することができます。

  • 選択されたクラスは、関連するすべての動画クリップを取得および分類するために、 INDEX ID と共にclassify(分類)エンドポイントに送信されます。 include_clips などの追加パラメータもこのエンドポイントに送信されます。

  • レスポンスには、ビデオID、クラス、信頼度(信頼スコア)、開始および終了時間、サムネイルURLなどが含まれます。

  • 結果のビデオIDに関連付けられたビデオURLを取得するには、ビデオ情報エンドポイントにアクセスする必要があります。

準備ステップ

  • Twelve Labs Playgroundでサインアップし、インデックスを作成します。

  • 分類に適切なオプションを選択します:Marengo 2.6 (Embedding Engine)。このエンジンは動画検索と分類に優れており、動画理解のための堅牢な基盤を提供します。

  • スポーツ動画クリップをアップロードします。必要に応じて、このチュートリアル用に提供されているサンプルクリップを使用してください:Sports Clips

  • Twelve Labs PlaygroundからAPIキーを取得します。

  • オリンピックのクリップが含まれているインデックスを開いて、 INDEX_ID を取得します。IDはURLに含まれています:https://playground.twelvelabs.io/indexes/{index_id}

  • メインファイルと共に、APIキーとINDEX_IDを設定した .env ファイルをセットアップします。

これらのステップが完了すれば、アプリケーションの開発に取り掛かる準備は完了です!

Twelvelabs_API=your_api_key_here
API_URL=your_api_url_here
INDEX_ID=your_index_id_here

分類のウォークスルー

1 - クライアントのインポート、検証、セットアップ

まず、必要なライブラリをインポートし、正しい環境を作成するために環境変数を設定することから始めます。開始するには:

import os
import requests
import glob
import requests
from twelvelabs import TwelveLabs
from twelvelabs.models.task import Task
import dotenv


API_KEY=os.getenv("Twelvelabs_API")
API_URL=os.getenv("API_URL")
INDEX_ID=os.getenv("INDEX_ID")

client = TwelveLabs(api_key=API_KEY)

# URL of the /indexes endpoint
INDEXES_URL = f"{API_URL}/indexes"
     
# Setting headers variables

default_header = {
    "x-api-key": API_KEY
}

依存関係がインポートされ、環境変数が正しくロードされ、適切なシークレット情報でAPIクライアントが初期化されました。インデックスにアクセスするためのベースURLと、APIリクエストのデフォルトヘッダーも設定されています。

この基盤が整ったので、インデックスを作成するなど、より具体的な分類タスクを進める準備が整いました。

2 - クラスの設定

以下は、オリンピックスポーツのクリップを分類するためのクラスのリストです:

# Categories for the classification of the Olympics Sports

CLASSES = [
    {
        "name": "AquaticSports",
        "prompts": [
            "swimming competition",
            "diving event",
            "water polo match",
            "synchronized swimming",
            "open water swimming"
        ]
    },
    {
        "name": "AthleticEvents",
        "prompts": [
            "track and field",
            "marathon running",
            "long jump competition",
            "javelin throw",
            "high jump event"
        ]
    },
    {
        "name": "GymnasticsEvents",
        "prompts": [
            "artistic gymnastics",
            "rhythmic gymnastics",
            "trampoline gymnastics",
            "balance beam routine",
            "floor exercise performance"
        ]
    },
    {
        "name": "CombatSports",
        "prompts": [
            "boxing match",
            "judo competition",
            "wrestling bout",
            "taekwondo fight",
            "fencing duel"
        ]
    },
    {
        "name": "TeamSports",
        "prompts": [
            "basketball game",
            "volleyball match",
            "football (soccer) match",
            "handball game",
            "field hockey competition"
        ]
    },
    {
        "name": "CyclingSports",
        "prompts": [
            "road cycling race",
            "track cycling event",
            "mountain bike competition",
            "BMX racing",
            "cycling time trial"
        ]
    },
    {
        "name": "RacquetSports",
        "prompts": [
            "tennis match",
            "badminton game",
            "table tennis competition",
            "squash game",
            "tennis doubles match"
        ]
    },
    {
        "name": "RowingAndSailing",
        "prompts": [
            "rowing competition",
            "sailing race",
            "canoe sprint",
            "kayak event",
            "windsurfing competition"
        ]
    }
]

3 - 特定のインデックスからすべての動画を分類するためのユーティリティ関数

# Utility function
def print_page(page):
    for data in page:
        print(f"video_id={data.video_id}")
        for cl in data.classes:
            print(
                f"  name={cl.name} score={cl.score} duration_ratio={cl.duration_ratio} clips={cl.clips.model_dump_json(indent=2)}"
            )


result = client.classify.index(
    index_id=INDEX_ID,
    options=["visual"],
    classes=CLASSES,
    include_clips=True
)

分類を実行するために、 client.classify.index() メソッドが呼び出されます。これは以前に定義された INDEX_ID を使用し、視覚的な分類(options=["visual"])に焦点を当て、結果にクリップ情報を含めます。 CLASSES パラメータは、分類に使用するクラスを指定します。

print_page(result) 関数は、分類結果を読みやすい形式で表示します。これは分類レスポンスを反復処理し、ビデオIDと、名前、スコア、デュレーション比(時間比率)、関連クリップなど、分類された各クラスに関する詳細情報を出力します。このアプローチにより、指定したインデックス内のすべてのビデオにわたる分類出力を簡単に分析できます。

4 - 結果を構造化して明確に出力する関数

print_classification_result() 関数は、TwelveLabs APIの分類結果を構造化された読みやすい形式で表示します。結果セット内の各動画を処理し、ビデオID、検出されたクラス、関連するスコアなどの重要情報を提示します。クラスごとに、全体的なスコアとデュレーション比を表示し、続いてスコアの降順でソートされた上位5つのクリップの詳細を表示します。これらのクリップ詳細には、開始時間と終了時間、および CLASSES 内の関連プロンプトが含まれます。この関数は、上位5つ以外に追加のクリップがあるかどうかも示します。

def print_classification_result(result) -> None:

    for video_data in result.data:
        print(f"Video ID: {video_data.video_id}")
        print("=" * 50)


        for class_data in video_data.classes:
            print(f"  Class: {class_data.name}")
            print(f"  Score: {class_data.score:.2f}")
            print(f"  Duration Ratio: {class_data.duration_ratio:.2f}")
            print("  Clips:")


            sorted_clips = sorted(class_data.clips, key=lambda x: x.score, reverse=True)


            for i, clip in enumerate(sorted_clips[:5], 1):  # Print top 5 clips
                print(f"    {i}. Score: {clip.score:.2f}")
                print(f"       Start: {clip.start:.2f}s, End: {clip.end:.2f}s")
                print(f"       Prompt: {clip.prompt}")


            if len(sorted_clips) > 5:
                print(f"    ... and {len(sorted_clips) - 5} more clips")


            print("-" * 40)


        print("\n")


    print(f"Total results: {result.page_info.total_results}")
    print(f"Page expires at: {result.page_info.page_expired_at}")
    print(f"Next page token: {result.page_info.next_page_token}")



上記の結果のサンプル出力は以下のようになります -

Video ID: 66c9b03be53394f4aaed82c1
==================================================
  Class: AquaticSports
  Score: 96.08
  Duration Ratio: 0.90
  Clips:
    1. Score: 85.74
       Start: 19.30s, End: 42.00s
       Prompt: water polo match
    2. Score: 85.38
       Start: 56.30s, End: 160.41s
       Prompt: water polo match
    3. Score: 85.25
       Start: 19.30s, End: 124.07s
       Prompt: synchronized swimming
    4. Score: 85.13
       Start: 0.00s, End: 24.83s
       Prompt: swimming competition
    5. Score: 85.08
       Start: 124.10s, End: 160.41s
       Prompt: synchronized swimming
    ... and 19 more clips

5 - インデックス内のすべての動画からの特定クラスのカテゴリ分け

このセクションでは、特定のインデックス内のすべての動画に対する「AquaticSports」の特定クラスのカテゴリ分けに焦点を当てます。このプロセスは、関連するプロンプトを持つ特定クラスの定義、分類の実行、および結果の表示という3つの主要なステップで構成されます。

この絞り込んだアプローチにより、水上スポーツのコンテンツを含む動画をより正確に分類できるようになります。 print_page() 関数はユーティリティツールとして機能し、分類結果を分かりやすく出力します。

CLASS = [
    {
        "name": "AquaticSports",
        "prompts": [
            "swimming competition",
            "diving event",
            "water polo match",
            "synchronized swimming",
            "open water swimming"
        ]
    }


]


def print_page(page):
    for data in page:
        print(f"video_id={data.video_id}")
        for cl in data.classes:
            print(
                f"  name={cl.name} score={cl.score} duration_ratio={cl.duration_ratio} detailed_scores={cl.detailed_scores.model_dump_json(indent=2)}"
            )
result = client.classify.index(
    index_id=INDEX_ID,
    options=["visual"],
    classes=CLASS,
    include_clips=True,
    show_detailed_score=True
)


print_classification_result(result)

上記のコードスニペットのサンプル出力セクションは、特定の CLASS に分類される動画を提供します。

Video ID: 66c9b03be53394f4aaed82c1
==================================================
  Class: AquaticSports
  Score: 96.08
  Duration Ratio: 0.90
  Clips:
    1. Score: 85.74
       Start: 19.30s, End: 42.00s
       Prompt: water polo match
    2. Score: 85.38
       Start: 56.30s, End: 160.41s
       Prompt: water polo match
    3. Score: 85.25
       Start: 19.30s, End: 124.07s
       Prompt: synchronized swimming
    4. Score: 85.13
       Start: 0.00s, End: 24.83s
       Prompt: swimming competition
    5. Score: 85.08
       Start: 124.10s, End: 160.41s
       Prompt: synchronized swimming
    ... and 19 more clips

Twelve Labs SDKを使用してclassifyエンドポイントを実装し、それと対話する方法を理解したところで、ユーザー向けに完全なStreamlitアプリケーションの開発に進みましょう。

Streamlitアプリケーションの作成

Streamlitは、そのシンプルさと迅速なプロトタイピング機能により、開発の初期段階に最適であるため、アプリケーションの構築用として選択されました。Streamlitを使用すると、最小限のコードでインタラクティブなWebインターフェイスを迅速に作成でき、分類タスクの結果をユーザーフレンドリーな方法で紹介するのに最適です。Twelve Labs SDKを組み合わせることで、高度にインタラクティブなWebインターフェイスを数分で作成できます。

Streamlitを使い始めるには、仮想環境を設定していることを確認し、次のコマンドを使用してStreamlitをインストールします:

pip install streamlit

詳細については、公式のStreamlitドキュメントを確認してください。

Streamlitアプリケーションを構築するには、次のファイル構造を使用します:

.
├── .env
├── app.py
├── requirements.txt
└── .gitignore

requirements.txt ファイルの内容は以下のとおりです:

streamlit
twelvelabs
requests
python-dotenv

これらの依存関係は、オリンピックの動画クリップ分類アプリケーションに必要なコア機能をカバーしています。

メインの app.py ファイルには、以下の機能が含まれています:

  • get_initial_classes(): 事前定義されたオリンピックスポーツカテゴリを設定します。

  • get_custom_classes() および add_custom_class(): 柔軟性を高めるために、ユーザー定義のカテゴリを管理します。

  • classify_videos(): TwelveLabs APIと連携して、選択したカテゴリに基づいて動画の分類を実行します。

  • get_video_urls(): 分類されたコンテンツのビデオURLを取得します。

  • render_video(): スムーズな再生のために、HLS.jsを使用して組み込みビデオプレーヤーを作成します。

# Import Necessary Dependencies
import streamlit as st
from twelvelabs import TwelveLabs
import requests
import os
from dotenv import load_dotenv

load_dotenv()

# Get the API Key from the Dashboard - https://playground.twelvelabs.io/dashboard/api-key
API_KEY = os.getenv("API_KEY")

# Create the INDEX ID as specified in the README.md and get the INDEX_ID
INDEX_ID = os.getenv("INDEX_ID")

client = TwelveLabs(api_key=API_KEY)

# Background Setting of the Application
page_element = """
<style>
[data-testid="stAppViewContainer"] {
    background-image: url("https://wallpapercave.com/wp/wp3589963.jpg");
    background-size: cover;
}
[data-testid="stHeader"] {
    background-color: rgba(0,0,0,0);
}
[data-testid="stToolbar"] {
    right: 2rem;
    background-image: url("");
    background-size: cover;
}
<&sol;style>
"""
st.markdown(page_element, unsafe_allow_html=True)


# Classes to classify the video into, there are categories name and 
# the prompts which specifc finds that factor to label that category

@st.cache_data
def get_initial_classes():
    return [
        {"name": "AquaticSports", "prompts": ["swimming competition", "diving event", "water polo match", "synchronized swimming", "open water swimming"]},
        {"name": "AthleticEvents", "prompts": ["track and field", "marathon running", "long jump competition", "javelin throw", "high jump event"]},
        {"name": "GymnasticsEvents", "prompts": ["artistic gymnastics", "rhythmic gymnastics", "trampoline gymnastics", "balance beam routine", "floor exercise performance"]},
        {"name": "CombatSports", "prompts": ["boxing match", "judo competition", "wrestling bout", "taekwondo fight", "fencing duel"]},
        {"name": "TeamSports", "prompts": ["basketball game", "volleyball match", "football (soccer) match", "handball game", "field hockey competition"]},
        {"name": "CyclingSports", "prompts": ["road cycling race", "track cycling event", "mountain bike competition", "BMX racing", "cycling time trial"]},
        {"name": "RacquetSports", "prompts": ["tennis match", "badminton game", "table tennis competition", "squash game", "tennis doubles match"]},
        {"name": "RowingAndSailing", "prompts": ["rowing competition", "sailing race", "canoe sprint", "kayak event", "windsurfing competition"]}
    ]

# Session State for the custom classes 
def get_custom_classes():
    if 'custom_classes' not in st.session_state:
        st.session_state.custom_classes = []
    return st.session_state.custom_classes

# Utitlity Function to add the custom classes in app
def add_custom_class(name, prompts):
    custom_classes = get_custom_classes()
    custom_classes.append({"name": name, "prompts": prompts})
    st.session_state.custom_classes = custom_classes
    st.session_state.new_class_added = True

# Utitlity Function to classify all the videos in the specified Index
def classify_videos(selected_classes):
    return client.classify.index(
        index_id=INDEX_ID,
        options=["visual"],
        classes=selected_classes,
        include_clips=True
    )

# To get the video urls from the resultant video id
def get_video_urls(video_ids):
    base_url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos/{{}}"
    headers = {"x-api-key": API_KEY, "Content-Type": "application/json"}
    video_urls = {}

    for video_id in video_ids:
        try:
            response = requests.get(base_url.format(video_id), headers=headers)
            response.raise_for_status()
            data = response.json()
            if 'hls' in data and 'video_url' in data['hls']:
                video_urls[video_id] = data['hls']['video_url']
            else:
                st.warning(f"No video URL found for video ID: {video_id}")
        except requests.exceptions.RequestException as e:
            st.error(f"Failed to get data for video ID: {video_id}. Error: {str(e)}")

    return video_urls

# Utitlity Function to Render the Video by the resultant video url
def render_video(video_url):
    hls_player = f"""
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"><&sol;script>
    <div style="width: 100%; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
        <video id="video" controls style="width: 100%; height: auto;"><&sol;video>
    <&sol;div>
    <script>
      var video = document.getElementById('video');
      var videoSrc = "{video_url}";
      if (Hls.isSupported()) {{
        var hls = new Hls();
        hls.loadSource(videoSrc);
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, function() {{
          video.pause();
        }});
      }}
      else if (video.canPlayType('application/vnd.apple.mpegurl')) {{
        video.src = videoSrc;
        video.addEventListener('loadedmetadata', function() {{
          video.pause();
        }});
      }}
    <&sol;script>
    """
    st.components.v1.html(hls_player, height=300)

get_initial_classes() 関数は、オリンピックスポーツの事前定義されたカテゴリの静的なリストを返します。Streamlitはこのデータをキャッシュするため、関数が実行されるのは1回だけで、その結果はメモリに保存されます。この関数をそれ以降に呼び出すと、キャッシュされた結果が返されるため、繰り返しの実行を避けることができます。

get_initial_classes() 関数はキャッシュされますが、get_custom_classes() はキャッシュされません。Streamlitのセッション状態に保存されるカスタムクラスは、アプリケーションの実行中に変化することが予想されるためです。

get_video_urls() はビデオIDのリストを受け取り、TwelveLabsに対してAPIコールを行って対応するHLS (HTTP Live Streaming) URLを取得し、潜在的なエラーや欠落しているURLを処理します。

render_video() は、複数のブラウザやデバイスと互換性のある、HLS.jsサポート付きのHTML5ビデオプレーヤーを作成します。必要に応じてネイティブのHLSにフォールバックします。

classify_videos() では、TwelveLabsクライアントを使用して、指定されたインデックス内のビデオを分類するために、ユーザーが選択したクラスが使用されます。この関数は視覚的な分類に焦点を当て、結果にクリップ情報を含めます。

# Main Function
def main():
    # Basic Markdown Setup for the Application
    st.markdown("""
    <style>
    .big-font {
        font-size: 40px !important;
        font-weight: bold;
        color: #000000;
        text-align: center;
        margin-bottom: 30px;
    }
    .subheader {
        font-size: 24px;
        font-weight: bold;
        color: #424242;
        margin-top: 20px;
        margin-bottom: 10px;
    }
    .stButton>button {
        width: 100%;
    }
    .video-info {
        background-color: #f0f0f0;
        border-radius: 10px;
        padding: 15px;
        margin-bottom: 20px;
    }
    .custom-box {
        background-color: #f9f9f9;
        border-radius: 10px;
        padding: 20px;
        margin-bottom: 20px;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .stTabs [data-baseweb="tab-list"] {
        gap: 24px;
    }
    .stTabs [data-baseweb="tab"] {
        height: 50px;
        white-space: pre-wrap;
        background-color: #f0f2f6;
        border-radius: 4px 4px 0px 0px;
        gap: 1px;
        padding-top: 10px;
        padding-bottom: 10px;
    }
    .stTabs [data-baseweb="tab-list"] button[aria-selected="true"] {
        background-color: #e8eaed;
    }
    <&sol;style>
    """, unsafe_allow_html=True)

    st.markdown('<p class="big-font">Olympics Classification w/t Twelve Labs<&sol;p>', unsafe_allow_html=True)
    
    # Updation of the classes
    CLASSES = get_initial_classes() + get_custom_classes()
    
    # Nav Tabs Creation
    tab1, tab2 = st.tabs(["Select Classes", "Add Custom Class"])
    
    with tab1:
        st.markdown('<p class="subheader">Select Classes<&sol;p>', unsafe_allow_html=True)
        with st.container():

            class_names = [cls["name"] for cls in CLASSES]
            # Multiselect option from the CLASSES
            selected_classes = st.multiselect("Choose one or more Olympic sports categories:", class_names)
            if st.button("Classify Videos", key="classify_button"):
                if selected_classes:
                    with st.spinner("Classifying videos..."):
                        selected_classes_with_prompts = [cls for cls in CLASSES if cls["name"] in selected_classes]
                        res = classify_videos(selected_classes_with_prompts)
                        
                        video_ids = [data.video_id for data in res.data]
                        # Retrieving the video urls from the resultant video which matches to the selected CLASSES
                        video_urls = get_video_urls(video_ids)
                        
                        st.markdown('<p class="subheader">Classified Videos<&sol;p>', unsafe_allow_html=True)
                        
                        # Iterating over to showcase the information for every resulatant video
                        for i, video_data in enumerate(res.data, 1):
                            video_id = video_data.video_id
                            video_url = video_urls.get(video_id, "URL not found")
                            
                            st.markdown(f"### Video {i}")
                            st.markdown('<div class="video-info">', unsafe_allow_html=True)
                            st.markdown(f"**Video ID:** {video_id}")
                            
                            for class_data in video_data.classes:
                                st.markdown(f"""
                                **Class:** {class_data.name}
                                - Score: {class_data.score:.2f}
                                - Duration Ratio: {class_data.duration_ratio:.2f}
                                """)
                            
                            if video_url != "URL not found":
                                render_video(video_url)
                            else:
                                st.warning("Video URL not available. Unable to render video.")
                            
                            st.markdown("---")
                        
                        st.success(f"Total videos classified: {len(res.data)}")
                else:
                    st.warning("Please select at least one class.")
            st.markdown('<&sol;div>', unsafe_allow_html=True)

        # Nav Tab for the addition of the Custom Classes to select from
        with tab2:
            st.markdown('<p class="subheader">Add Custom Class<&sol;p>', unsafe_allow_html=True)
            with st.container():
                custom_class_name = st.text_input("Enter custom class name")
                custom_class_prompts = st.text_input("Enter custom class prompts (comma-separated)")
                if st.button("Add Custom Class"):
                    if custom_class_name and custom_class_prompts:
                        add_custom_class(custom_class_name, custom_class_prompts.split(','))
                        st.success(f"Custom class '{custom_class_name}' added successfully!")
                        st.experimental_rerun()
                    else:
                        st.warning("Please enter both class name and prompts.")
                st.markdown('<&sol;div>', unsafe_allow_html=True)

    if st.session_state.get('new_class_added', False):
        st.session_state.new_class_added = False
        st.experimental_rerun()

if __name__ == "__main__":
    main()

アプリケーションのメイン関数では、指示の正しい順序だけでなく、CSSとレイアウトに大きく重点を置いています。ユーザーは動画分類タブの下にある [Add Custom Class (カスタムクラスの追加)] タブから新しい分類カテゴリを追加できます。[Select Classes (クラスの選択)] タブを使用すると、ユーザーは定義されたカテゴリおよびカスタムカテゴリから選択できます。

ユーザーが「Classify Videos(動画の分類)」ボタンをクリックすると、アプリケーションは次の処理を行います:

  • 選択されたクラスを取得します。

  • TwelveLabs APIを呼び出して動画を分類します(classify_videos() 関数)。

  • 分類された動画の動画URLを取得します。

  • 分類結果を表示し、動画をレンダリングします。

最終的なアプリケーション結果は以下の通りです —

チュートリアルをさらに試すためのアイデア

アプリケーションの動作手順と開発プロセスを理解することで、革新的なアイデアを実装し、現実世界にインパクトを与える準備が整います。このチュートリアルブログに類似した、さらに発展させることができるいくつかのユースケースのアイデアをご紹介します:

🔍 ビデオ検索エンジン:ビデオコンテンツの検索可能なデータベースを作成し、ユーザーが大容量の動画コレクションから特定のシーンやトピックを見つけられるようにします。

🎥 防犯カメラ映像分析:防犯カメラ映像内の特定のイベントや行動を検出してカテゴリ分けします。

💃 ダンスムーブ分類:ダンス動画からさまざまなダンススタイルや特定の動きを識別し、カテゴリ分けします。

結論

このブログ記事では、Twelve Labsを活用した分類タスクを使って、アプリケーションがどのように構築されるのか、その動作手順を詳細に解説しました。チュートリアルにお付き合いいただきありがとうございました。ユーザーエクスペリエンスの向上やさまざまな問題の解決に関する皆様のアイデアを楽しみにしています。

オリンピックのビデオ映像を整理してメダルを獲得することを夢見たことはありませんか? 🥇 さあ、表彰台に立つチャンスです!

オリンピック動画クリップ分類アプリケーションは、スポーツ映像をカテゴリ分けする退屈なプロセスを効率化することを目指しています。Twelve LabsのMarengo 2.6埋め込みモデルを使用し、このアプリは動画クリップからオリンピック競技を迅速に分類します。

画面上のテキスト、会話、視覚情報を分析することにより、アプリケーションは動画クリップを簡単にカテゴリ分けします。このチュートリアルでは、研究者、スポーツ愛好家、放送局がオリンピックコンテンツと対話する方法に革命をもたらすStreamlitアプリケーションの作成方法をガイドします。ユーザー定義のカテゴリに基づいて動画を分類するアプリの構築方法を学びます。アプリケーションの詳細なデモンストレーション動画は、以下に提供されています。

アプリケーションのデモはこちらでご覧いただけます:Olympics Application。また、こちらのReplitテンプレートを使って触ってみることもできます。

前提条件

  • Twelve Labs Playgroundにサインアップして、APIキーを生成します。

  • ノートブックとこのアプリケーションのリポジトリはGitHubにあります。

  • Streamlitアプリケーションは、Python、HTML、JavaScriptを使用しています。



アプリケーションの仕組み

本セクションでは、オリンピック動画クリップを分類するためのアプリケーションフローの概要を説明します。

アプリケーションは分類検索エンジンを使用しており、さまざまな潜在的用途があります。このタスクでは、オリンピックのスポーツ動画クリップの分類と取得に焦点を当てます。最初の重要なステップは、インデックスの作成です。

  • これを行うには、Twelve Labs Playgroundにアクセスしてください。

  • [New Index (新しいインデックス)] を作成し、インデックス作成と分類に適した Marengo 2.6 (Embedding Engine) を選択します。

  • すべてのスポーツ動画がこのインデックスにアップロードされたら、次に進むことができます。

次のセクションでは、インデックスIDを作成して実装する方法をステップバイステップで詳しく説明します。一般的なアプリケーションワークフローの概要は以下のとおりです:

  • ユーザーには2つのオプションがあります。マルチセレクトメニューから表示したいスポーツのタイプを選択するか、独自のカスタムクラスを追加することができます。

  • 選択されたクラスは、関連するすべての動画クリップを取得および分類するために、 INDEX ID と共にclassify(分類)エンドポイントに送信されます。 include_clips などの追加パラメータもこのエンドポイントに送信されます。

  • レスポンスには、ビデオID、クラス、信頼度(信頼スコア)、開始および終了時間、サムネイルURLなどが含まれます。

  • 結果のビデオIDに関連付けられたビデオURLを取得するには、ビデオ情報エンドポイントにアクセスする必要があります。

準備ステップ

  • Twelve Labs Playgroundでサインアップし、インデックスを作成します。

  • 分類に適切なオプションを選択します:Marengo 2.6 (Embedding Engine)。このエンジンは動画検索と分類に優れており、動画理解のための堅牢な基盤を提供します。

  • スポーツ動画クリップをアップロードします。必要に応じて、このチュートリアル用に提供されているサンプルクリップを使用してください:Sports Clips

  • Twelve Labs PlaygroundからAPIキーを取得します。

  • オリンピックのクリップが含まれているインデックスを開いて、 INDEX_ID を取得します。IDはURLに含まれています:https://playground.twelvelabs.io/indexes/{index_id}

  • メインファイルと共に、APIキーとINDEX_IDを設定した .env ファイルをセットアップします。

これらのステップが完了すれば、アプリケーションの開発に取り掛かる準備は完了です!

Twelvelabs_API=your_api_key_here
API_URL=your_api_url_here
INDEX_ID=your_index_id_here

分類のウォークスルー

1 - クライアントのインポート、検証、セットアップ

まず、必要なライブラリをインポートし、正しい環境を作成するために環境変数を設定することから始めます。開始するには:

import os
import requests
import glob
import requests
from twelvelabs import TwelveLabs
from twelvelabs.models.task import Task
import dotenv


API_KEY=os.getenv("Twelvelabs_API")
API_URL=os.getenv("API_URL")
INDEX_ID=os.getenv("INDEX_ID")

client = TwelveLabs(api_key=API_KEY)

# URL of the /indexes endpoint
INDEXES_URL = f"{API_URL}/indexes"
     
# Setting headers variables

default_header = {
    "x-api-key": API_KEY
}

依存関係がインポートされ、環境変数が正しくロードされ、適切なシークレット情報でAPIクライアントが初期化されました。インデックスにアクセスするためのベースURLと、APIリクエストのデフォルトヘッダーも設定されています。

この基盤が整ったので、インデックスを作成するなど、より具体的な分類タスクを進める準備が整いました。

2 - クラスの設定

以下は、オリンピックスポーツのクリップを分類するためのクラスのリストです:

# Categories for the classification of the Olympics Sports

CLASSES = [
    {
        "name": "AquaticSports",
        "prompts": [
            "swimming competition",
            "diving event",
            "water polo match",
            "synchronized swimming",
            "open water swimming"
        ]
    },
    {
        "name": "AthleticEvents",
        "prompts": [
            "track and field",
            "marathon running",
            "long jump competition",
            "javelin throw",
            "high jump event"
        ]
    },
    {
        "name": "GymnasticsEvents",
        "prompts": [
            "artistic gymnastics",
            "rhythmic gymnastics",
            "trampoline gymnastics",
            "balance beam routine",
            "floor exercise performance"
        ]
    },
    {
        "name": "CombatSports",
        "prompts": [
            "boxing match",
            "judo competition",
            "wrestling bout",
            "taekwondo fight",
            "fencing duel"
        ]
    },
    {
        "name": "TeamSports",
        "prompts": [
            "basketball game",
            "volleyball match",
            "football (soccer) match",
            "handball game",
            "field hockey competition"
        ]
    },
    {
        "name": "CyclingSports",
        "prompts": [
            "road cycling race",
            "track cycling event",
            "mountain bike competition",
            "BMX racing",
            "cycling time trial"
        ]
    },
    {
        "name": "RacquetSports",
        "prompts": [
            "tennis match",
            "badminton game",
            "table tennis competition",
            "squash game",
            "tennis doubles match"
        ]
    },
    {
        "name": "RowingAndSailing",
        "prompts": [
            "rowing competition",
            "sailing race",
            "canoe sprint",
            "kayak event",
            "windsurfing competition"
        ]
    }
]

3 - 特定のインデックスからすべての動画を分類するためのユーティリティ関数

# Utility function
def print_page(page):
    for data in page:
        print(f"video_id={data.video_id}")
        for cl in data.classes:
            print(
                f"  name={cl.name} score={cl.score} duration_ratio={cl.duration_ratio} clips={cl.clips.model_dump_json(indent=2)}"
            )


result = client.classify.index(
    index_id=INDEX_ID,
    options=["visual"],
    classes=CLASSES,
    include_clips=True
)

分類を実行するために、 client.classify.index() メソッドが呼び出されます。これは以前に定義された INDEX_ID を使用し、視覚的な分類(options=["visual"])に焦点を当て、結果にクリップ情報を含めます。 CLASSES パラメータは、分類に使用するクラスを指定します。

print_page(result) 関数は、分類結果を読みやすい形式で表示します。これは分類レスポンスを反復処理し、ビデオIDと、名前、スコア、デュレーション比(時間比率)、関連クリップなど、分類された各クラスに関する詳細情報を出力します。このアプローチにより、指定したインデックス内のすべてのビデオにわたる分類出力を簡単に分析できます。

4 - 結果を構造化して明確に出力する関数

print_classification_result() 関数は、TwelveLabs APIの分類結果を構造化された読みやすい形式で表示します。結果セット内の各動画を処理し、ビデオID、検出されたクラス、関連するスコアなどの重要情報を提示します。クラスごとに、全体的なスコアとデュレーション比を表示し、続いてスコアの降順でソートされた上位5つのクリップの詳細を表示します。これらのクリップ詳細には、開始時間と終了時間、および CLASSES 内の関連プロンプトが含まれます。この関数は、上位5つ以外に追加のクリップがあるかどうかも示します。

def print_classification_result(result) -> None:

    for video_data in result.data:
        print(f"Video ID: {video_data.video_id}")
        print("=" * 50)


        for class_data in video_data.classes:
            print(f"  Class: {class_data.name}")
            print(f"  Score: {class_data.score:.2f}")
            print(f"  Duration Ratio: {class_data.duration_ratio:.2f}")
            print("  Clips:")


            sorted_clips = sorted(class_data.clips, key=lambda x: x.score, reverse=True)


            for i, clip in enumerate(sorted_clips[:5], 1):  # Print top 5 clips
                print(f"    {i}. Score: {clip.score:.2f}")
                print(f"       Start: {clip.start:.2f}s, End: {clip.end:.2f}s")
                print(f"       Prompt: {clip.prompt}")


            if len(sorted_clips) > 5:
                print(f"    ... and {len(sorted_clips) - 5} more clips")


            print("-" * 40)


        print("\n")


    print(f"Total results: {result.page_info.total_results}")
    print(f"Page expires at: {result.page_info.page_expired_at}")
    print(f"Next page token: {result.page_info.next_page_token}")



上記の結果のサンプル出力は以下のようになります -

Video ID: 66c9b03be53394f4aaed82c1
==================================================
  Class: AquaticSports
  Score: 96.08
  Duration Ratio: 0.90
  Clips:
    1. Score: 85.74
       Start: 19.30s, End: 42.00s
       Prompt: water polo match
    2. Score: 85.38
       Start: 56.30s, End: 160.41s
       Prompt: water polo match
    3. Score: 85.25
       Start: 19.30s, End: 124.07s
       Prompt: synchronized swimming
    4. Score: 85.13
       Start: 0.00s, End: 24.83s
       Prompt: swimming competition
    5. Score: 85.08
       Start: 124.10s, End: 160.41s
       Prompt: synchronized swimming
    ... and 19 more clips

5 - インデックス内のすべての動画からの特定クラスのカテゴリ分け

このセクションでは、特定のインデックス内のすべての動画に対する「AquaticSports」の特定クラスのカテゴリ分けに焦点を当てます。このプロセスは、関連するプロンプトを持つ特定クラスの定義、分類の実行、および結果の表示という3つの主要なステップで構成されます。

この絞り込んだアプローチにより、水上スポーツのコンテンツを含む動画をより正確に分類できるようになります。 print_page() 関数はユーティリティツールとして機能し、分類結果を分かりやすく出力します。

CLASS = [
    {
        "name": "AquaticSports",
        "prompts": [
            "swimming competition",
            "diving event",
            "water polo match",
            "synchronized swimming",
            "open water swimming"
        ]
    }


]


def print_page(page):
    for data in page:
        print(f"video_id={data.video_id}")
        for cl in data.classes:
            print(
                f"  name={cl.name} score={cl.score} duration_ratio={cl.duration_ratio} detailed_scores={cl.detailed_scores.model_dump_json(indent=2)}"
            )
result = client.classify.index(
    index_id=INDEX_ID,
    options=["visual"],
    classes=CLASS,
    include_clips=True,
    show_detailed_score=True
)


print_classification_result(result)

上記のコードスニペットのサンプル出力セクションは、特定の CLASS に分類される動画を提供します。

Video ID: 66c9b03be53394f4aaed82c1
==================================================
  Class: AquaticSports
  Score: 96.08
  Duration Ratio: 0.90
  Clips:
    1. Score: 85.74
       Start: 19.30s, End: 42.00s
       Prompt: water polo match
    2. Score: 85.38
       Start: 56.30s, End: 160.41s
       Prompt: water polo match
    3. Score: 85.25
       Start: 19.30s, End: 124.07s
       Prompt: synchronized swimming
    4. Score: 85.13
       Start: 0.00s, End: 24.83s
       Prompt: swimming competition
    5. Score: 85.08
       Start: 124.10s, End: 160.41s
       Prompt: synchronized swimming
    ... and 19 more clips

Twelve Labs SDKを使用してclassifyエンドポイントを実装し、それと対話する方法を理解したところで、ユーザー向けに完全なStreamlitアプリケーションの開発に進みましょう。

Streamlitアプリケーションの作成

Streamlitは、そのシンプルさと迅速なプロトタイピング機能により、開発の初期段階に最適であるため、アプリケーションの構築用として選択されました。Streamlitを使用すると、最小限のコードでインタラクティブなWebインターフェイスを迅速に作成でき、分類タスクの結果をユーザーフレンドリーな方法で紹介するのに最適です。Twelve Labs SDKを組み合わせることで、高度にインタラクティブなWebインターフェイスを数分で作成できます。

Streamlitを使い始めるには、仮想環境を設定していることを確認し、次のコマンドを使用してStreamlitをインストールします:

pip install streamlit

詳細については、公式のStreamlitドキュメントを確認してください。

Streamlitアプリケーションを構築するには、次のファイル構造を使用します:

.
├── .env
├── app.py
├── requirements.txt
└── .gitignore

requirements.txt ファイルの内容は以下のとおりです:

streamlit
twelvelabs
requests
python-dotenv

これらの依存関係は、オリンピックの動画クリップ分類アプリケーションに必要なコア機能をカバーしています。

メインの app.py ファイルには、以下の機能が含まれています:

  • get_initial_classes(): 事前定義されたオリンピックスポーツカテゴリを設定します。

  • get_custom_classes() および add_custom_class(): 柔軟性を高めるために、ユーザー定義のカテゴリを管理します。

  • classify_videos(): TwelveLabs APIと連携して、選択したカテゴリに基づいて動画の分類を実行します。

  • get_video_urls(): 分類されたコンテンツのビデオURLを取得します。

  • render_video(): スムーズな再生のために、HLS.jsを使用して組み込みビデオプレーヤーを作成します。

# Import Necessary Dependencies
import streamlit as st
from twelvelabs import TwelveLabs
import requests
import os
from dotenv import load_dotenv

load_dotenv()

# Get the API Key from the Dashboard - https://playground.twelvelabs.io/dashboard/api-key
API_KEY = os.getenv("API_KEY")

# Create the INDEX ID as specified in the README.md and get the INDEX_ID
INDEX_ID = os.getenv("INDEX_ID")

client = TwelveLabs(api_key=API_KEY)

# Background Setting of the Application
page_element = """
<style>
[data-testid="stAppViewContainer"] {
    background-image: url("https://wallpapercave.com/wp/wp3589963.jpg");
    background-size: cover;
}
[data-testid="stHeader"] {
    background-color: rgba(0,0,0,0);
}
[data-testid="stToolbar"] {
    right: 2rem;
    background-image: url("");
    background-size: cover;
}
<&sol;style>
"""
st.markdown(page_element, unsafe_allow_html=True)


# Classes to classify the video into, there are categories name and 
# the prompts which specifc finds that factor to label that category

@st.cache_data
def get_initial_classes():
    return [
        {"name": "AquaticSports", "prompts": ["swimming competition", "diving event", "water polo match", "synchronized swimming", "open water swimming"]},
        {"name": "AthleticEvents", "prompts": ["track and field", "marathon running", "long jump competition", "javelin throw", "high jump event"]},
        {"name": "GymnasticsEvents", "prompts": ["artistic gymnastics", "rhythmic gymnastics", "trampoline gymnastics", "balance beam routine", "floor exercise performance"]},
        {"name": "CombatSports", "prompts": ["boxing match", "judo competition", "wrestling bout", "taekwondo fight", "fencing duel"]},
        {"name": "TeamSports", "prompts": ["basketball game", "volleyball match", "football (soccer) match", "handball game", "field hockey competition"]},
        {"name": "CyclingSports", "prompts": ["road cycling race", "track cycling event", "mountain bike competition", "BMX racing", "cycling time trial"]},
        {"name": "RacquetSports", "prompts": ["tennis match", "badminton game", "table tennis competition", "squash game", "tennis doubles match"]},
        {"name": "RowingAndSailing", "prompts": ["rowing competition", "sailing race", "canoe sprint", "kayak event", "windsurfing competition"]}
    ]

# Session State for the custom classes 
def get_custom_classes():
    if 'custom_classes' not in st.session_state:
        st.session_state.custom_classes = []
    return st.session_state.custom_classes

# Utitlity Function to add the custom classes in app
def add_custom_class(name, prompts):
    custom_classes = get_custom_classes()
    custom_classes.append({"name": name, "prompts": prompts})
    st.session_state.custom_classes = custom_classes
    st.session_state.new_class_added = True

# Utitlity Function to classify all the videos in the specified Index
def classify_videos(selected_classes):
    return client.classify.index(
        index_id=INDEX_ID,
        options=["visual"],
        classes=selected_classes,
        include_clips=True
    )

# To get the video urls from the resultant video id
def get_video_urls(video_ids):
    base_url = f"https://api.twelvelabs.io/v1.2/indexes/{INDEX_ID}/videos/{{}}"
    headers = {"x-api-key": API_KEY, "Content-Type": "application/json"}
    video_urls = {}

    for video_id in video_ids:
        try:
            response = requests.get(base_url.format(video_id), headers=headers)
            response.raise_for_status()
            data = response.json()
            if 'hls' in data and 'video_url' in data['hls']:
                video_urls[video_id] = data['hls']['video_url']
            else:
                st.warning(f"No video URL found for video ID: {video_id}")
        except requests.exceptions.RequestException as e:
            st.error(f"Failed to get data for video ID: {video_id}. Error: {str(e)}")

    return video_urls

# Utitlity Function to Render the Video by the resultant video url
def render_video(video_url):
    hls_player = f"""
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"><&sol;script>
    <div style="width: 100%; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
        <video id="video" controls style="width: 100%; height: auto;"><&sol;video>
    <&sol;div>
    <script>
      var video = document.getElementById('video');
      var videoSrc = "{video_url}";
      if (Hls.isSupported()) {{
        var hls = new Hls();
        hls.loadSource(videoSrc);
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, function() {{
          video.pause();
        }});
      }}
      else if (video.canPlayType('application/vnd.apple.mpegurl')) {{
        video.src = videoSrc;
        video.addEventListener('loadedmetadata', function() {{
          video.pause();
        }});
      }}
    <&sol;script>
    """
    st.components.v1.html(hls_player, height=300)

get_initial_classes() 関数は、オリンピックスポーツの事前定義されたカテゴリの静的なリストを返します。Streamlitはこのデータをキャッシュするため、関数が実行されるのは1回だけで、その結果はメモリに保存されます。この関数をそれ以降に呼び出すと、キャッシュされた結果が返されるため、繰り返しの実行を避けることができます。

get_initial_classes() 関数はキャッシュされますが、get_custom_classes() はキャッシュされません。Streamlitのセッション状態に保存されるカスタムクラスは、アプリケーションの実行中に変化することが予想されるためです。

get_video_urls() はビデオIDのリストを受け取り、TwelveLabsに対してAPIコールを行って対応するHLS (HTTP Live Streaming) URLを取得し、潜在的なエラーや欠落しているURLを処理します。

render_video() は、複数のブラウザやデバイスと互換性のある、HLS.jsサポート付きのHTML5ビデオプレーヤーを作成します。必要に応じてネイティブのHLSにフォールバックします。

classify_videos() では、TwelveLabsクライアントを使用して、指定されたインデックス内のビデオを分類するために、ユーザーが選択したクラスが使用されます。この関数は視覚的な分類に焦点を当て、結果にクリップ情報を含めます。

# Main Function
def main():
    # Basic Markdown Setup for the Application
    st.markdown("""
    <style>
    .big-font {
        font-size: 40px !important;
        font-weight: bold;
        color: #000000;
        text-align: center;
        margin-bottom: 30px;
    }
    .subheader {
        font-size: 24px;
        font-weight: bold;
        color: #424242;
        margin-top: 20px;
        margin-bottom: 10px;
    }
    .stButton>button {
        width: 100%;
    }
    .video-info {
        background-color: #f0f0f0;
        border-radius: 10px;
        padding: 15px;
        margin-bottom: 20px;
    }
    .custom-box {
        background-color: #f9f9f9;
        border-radius: 10px;
        padding: 20px;
        margin-bottom: 20px;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .stTabs [data-baseweb="tab-list"] {
        gap: 24px;
    }
    .stTabs [data-baseweb="tab"] {
        height: 50px;
        white-space: pre-wrap;
        background-color: #f0f2f6;
        border-radius: 4px 4px 0px 0px;
        gap: 1px;
        padding-top: 10px;
        padding-bottom: 10px;
    }
    .stTabs [data-baseweb="tab-list"] button[aria-selected="true"] {
        background-color: #e8eaed;
    }
    <&sol;style>
    """, unsafe_allow_html=True)

    st.markdown('<p class="big-font">Olympics Classification w/t Twelve Labs<&sol;p>', unsafe_allow_html=True)
    
    # Updation of the classes
    CLASSES = get_initial_classes() + get_custom_classes()
    
    # Nav Tabs Creation
    tab1, tab2 = st.tabs(["Select Classes", "Add Custom Class"])
    
    with tab1:
        st.markdown('<p class="subheader">Select Classes<&sol;p>', unsafe_allow_html=True)
        with st.container():

            class_names = [cls["name"] for cls in CLASSES]
            # Multiselect option from the CLASSES
            selected_classes = st.multiselect("Choose one or more Olympic sports categories:", class_names)
            if st.button("Classify Videos", key="classify_button"):
                if selected_classes:
                    with st.spinner("Classifying videos..."):
                        selected_classes_with_prompts = [cls for cls in CLASSES if cls["name"] in selected_classes]
                        res = classify_videos(selected_classes_with_prompts)
                        
                        video_ids = [data.video_id for data in res.data]
                        # Retrieving the video urls from the resultant video which matches to the selected CLASSES
                        video_urls = get_video_urls(video_ids)
                        
                        st.markdown('<p class="subheader">Classified Videos<&sol;p>', unsafe_allow_html=True)
                        
                        # Iterating over to showcase the information for every resulatant video
                        for i, video_data in enumerate(res.data, 1):
                            video_id = video_data.video_id
                            video_url = video_urls.get(video_id, "URL not found")
                            
                            st.markdown(f"### Video {i}")
                            st.markdown('<div class="video-info">', unsafe_allow_html=True)
                            st.markdown(f"**Video ID:** {video_id}")
                            
                            for class_data in video_data.classes:
                                st.markdown(f"""
                                **Class:** {class_data.name}
                                - Score: {class_data.score:.2f}
                                - Duration Ratio: {class_data.duration_ratio:.2f}
                                """)
                            
                            if video_url != "URL not found":
                                render_video(video_url)
                            else:
                                st.warning("Video URL not available. Unable to render video.")
                            
                            st.markdown("---")
                        
                        st.success(f"Total videos classified: {len(res.data)}")
                else:
                    st.warning("Please select at least one class.")
            st.markdown('<&sol;div>', unsafe_allow_html=True)

        # Nav Tab for the addition of the Custom Classes to select from
        with tab2:
            st.markdown('<p class="subheader">Add Custom Class<&sol;p>', unsafe_allow_html=True)
            with st.container():
                custom_class_name = st.text_input("Enter custom class name")
                custom_class_prompts = st.text_input("Enter custom class prompts (comma-separated)")
                if st.button("Add Custom Class"):
                    if custom_class_name and custom_class_prompts:
                        add_custom_class(custom_class_name, custom_class_prompts.split(','))
                        st.success(f"Custom class '{custom_class_name}' added successfully!")
                        st.experimental_rerun()
                    else:
                        st.warning("Please enter both class name and prompts.")
                st.markdown('<&sol;div>', unsafe_allow_html=True)

    if st.session_state.get('new_class_added', False):
        st.session_state.new_class_added = False
        st.experimental_rerun()

if __name__ == "__main__":
    main()

アプリケーションのメイン関数では、指示の正しい順序だけでなく、CSSとレイアウトに大きく重点を置いています。ユーザーは動画分類タブの下にある [Add Custom Class (カスタムクラスの追加)] タブから新しい分類カテゴリを追加できます。[Select Classes (クラスの選択)] タブを使用すると、ユーザーは定義されたカテゴリおよびカスタムカテゴリから選択できます。

ユーザーが「Classify Videos(動画の分類)」ボタンをクリックすると、アプリケーションは次の処理を行います:

  • 選択されたクラスを取得します。

  • TwelveLabs APIを呼び出して動画を分類します(classify_videos() 関数)。

  • 分類された動画の動画URLを取得します。

  • 分類結果を表示し、動画をレンダリングします。

最終的なアプリケーション結果は以下の通りです —

チュートリアルをさらに試すためのアイデア

アプリケーションの動作手順と開発プロセスを理解することで、革新的なアイデアを実装し、現実世界にインパクトを与える準備が整います。このチュートリアルブログに類似した、さらに発展させることができるいくつかのユースケースのアイデアをご紹介します:

🔍 ビデオ検索エンジン:ビデオコンテンツの検索可能なデータベースを作成し、ユーザーが大容量の動画コレクションから特定のシーンやトピックを見つけられるようにします。

🎥 防犯カメラ映像分析:防犯カメラ映像内の特定のイベントや行動を検出してカテゴリ分けします。

💃 ダンスムーブ分類:ダンス動画からさまざまなダンススタイルや特定の動きを識別し、カテゴリ分けします。

結論

このブログ記事では、Twelve Labsを活用した分類タスクを使って、アプリケーションがどのように構築されるのか、その動作手順を詳細に解説しました。チュートリアルにお付き合いいただきありがとうございました。ユーザーエクスペリエンスの向上やさまざまな問題の解決に関する皆様のアイデアを楽しみにしています。