すべてのプロダクト
Search
ドキュメントセンター

Object Storage Service:チュートリアル:IPC デバイス向けスマートな意味検索システムの構築

最終更新日:Mar 20, 2026

IP カメラは継続的なビデオストリームを生成するため、大規模な映像データに対する手動レビューは現実的ではありません。本チュートリアルでは、OSS のデータインデックス機能を活用して、自然言語による説明(例:「駐車中の車のある庭」)で映像ライブラリを検索できる意味検索システムを構築する方法を説明します。

ソリューションの概要

image

本ソリューションは以下の 2 つのステップで構成されます:

  1. バケットの作成と動画のアップロード:IP カメラから取得した生の動画ファイルを OSS バケットに格納します。

  2. AISearch 機能の有効化:対象バケットに対して AISearch を有効化し、OSS が動画コンテンツをインデックス化して、自然言語による検索を可能にします。

主な機能

機能説明
意味検索自然言語による説明および複数条件の組み合わせで動画を検索できます。手動レビューを行わずに、特定のフレームを迅速に特定できます。
マルチモーダル検索単一のインターフェイスから動画、画像、テキストを横断的に検索できます。技術的ハードルおよび運用・保守(O&M)コストを低減します。
水平方向のスケーリングOSS は、大規模かつ増加傾向にある動画データセットに対応可能な、無制限の容量と優れた拡張性を提供します。

前提条件

開始する前に、以下の条件を満たしていることを確認してください。

  • OSS へのアクセス権限を持つ Alibaba Cloud アカウント

  • OSS コンソールへのアクセス権限

  • アップロード準備が完了した動画ファイル(ステップ 1 でサンプルファイルを提供)

ステップ 1:バケットの作成と動画のアップロード

  1. OSS コンソールにログインします。

  2. バケットページに移動し、バケットの作成をクリックします。

  3. バケットの作成ページで、ビジネス内容に関連付けたバケット名(例:ipc-videos-oss-metaquery-demo)を入力します。その他のパラメーターはすべてデフォルト設定のままとします。

  4. 作成をクリックします。バケットが作成された後、バケットの表示をクリックします。

  5. [ファイル一覧] ページで、[ファイルをアップロード] をクリックし、[ファイルをスキャン] を選択します。アップロードする動画ファイル(例:[Video A.mp4]、[Video B.mp4]、[Video C.mp4])を選択します。デフォルト設定をそのままにして、[ファイルをアップロード] をクリックします。

  6. (任意)アップロード済みの動画ファイルにタグを追加します。タグを付与することで、インデックス作成および検索時にカメラ ID や所在地などの属性で動画をフィルターできます。操作列の対象ファイルに対して、more > その他 > タグ付けを選択します。ダイアログボックスで、タグのキーと値のペアを追加します(例: OK をクリックします)。

    • インデックス作成時のフィルター条件として使用する場合、キーを need-seek、値を true に設定します。

    • 検索時にカメラごとに結果をフィルターする場合、キーを camera、値を camera-a に設定します。

ステップ 2:AISearch 機能の有効化

  1. 左側ナビゲーションウィンドウで、ファイル管理 > データインデックス を選択します。

  2. データインデックスページで、初めてデータインデックスを利用する場合は、画面上の指示に従って AliyunMetaQueryDefaultRole ロールに必要な権限を付与します。これにより、OSS がご利用のバケット内のデータを管理できるようになります。権限付与後、データインデックスの有効化 をクリックし、AISearch を選択します。

  3. (任意)AI コンテンツ認識:ニーズに応じて、画像コンテンツ認識 または 動画コンテンツ認識 を選択します。

  4. (任意)ファイルフィルタリングルール:特定の条件に一致するファイルのみを対象に AI 分析を実行するように設定できます。最大 5 つのルールを設定可能です。フィルター基準には、プレフィックス、ファイルサイズ、タグ、最終更新日時(LastModifiedTime)が含まれます。たとえば、キー need-seek、値 true のタグフィルタリングルールを追加すると、該当タグが付与されたファイルのみがインデックス化されます。

    ファイルフィルタリングが有効化されている場合、データインデックス、AISearch、およびコンテンツ認識の課金対象は、フィルター適用後のファイル数のみとなります。
  5. 有効化 をクリックします。

メタデータインデックスの構築には時間がかかります。所要時間はバケット内のオブジェクト数によって異なります。ステータスを確認するには、ページを更新してください。
imageimage

結果の検証

単に説明的なテキスト(例: a yard with a parked car)を入力するだけで、システムが説明に一致する関連する動画を返します。

  1. バケットページで、ご利用のバケット名をクリックします。

  2. ファイルページで、動画が正常にアップロードされたことを確認します。

  3. 左側ナビゲーションウィンドウで、ファイル管理 > データインデックス を選択します。

  4. データインデックスページの検索ボックスに、a yard with a parked car を入力します。メディアタイプ では、Video を選択します。

  5. (任意) オブジェクトタグ付け で、キーを camera、値を camera-a に設定します。この設定により、camera=camera-a のタグが付与された動画のみが返されます。

  6. 検索 をクリックします。

  7. 検索結果からファイルパスをコピーします。その後、オブジェクトページに戻り、検索ボックスにパスを貼り付けて検索することで、該当する動画を表示できます。

2025-05-23_16-41-02 (1)

本番環境への適用

上記のコンソール操作は、機能の検証を目的としています。本番環境では、OSS のソフトウェア開発キット(SDK)を活用して、動画のアップロード、タグ管理、検索クエリを自動化してください。

OSS への動画アップロード

IP カメラは継続的なビデオストリームを生成します。OSS Python SDK を使用して、動画セグメントをバケットに自動的にアップロードします。

以下の例では、ファイルアップロードマネージャーを使用して動画をアップロードしています:

import argparse
import alibabacloud_oss_v2 as oss
from alibabacloud_oss_v2.models import ListObjectsRequest, PutObjectTaggingRequest, Tagging, TagSet, Tag, PutObjectRequest
import os

def upload_video(client, bucket, video_config):
    """動画のアップロード"""
    try:
        # ファイルの存在確認
        if not os.path.exists(video_config['path']):
            print(f'ファイルが存在しません: {video_config["path"]}')
            return False

        # アップロード対象のオブジェクトを作成
        uploader = client.uploader()

        # アップロードリクエストの実行 - filepath パラメーターを正しく指定
        result = uploader.upload_file(
            filepath=video_config['path'],  # filepath パラメーターを追加
            request=PutObjectRequest(
                bucket=bucket,
                key=video_config['key']
            )
        )

        print(f'{video_config["key"]} のアップロードが成功しました:')
        print(f'状態コード: {result.status_code},'
              f' リクエスト ID: {result.request_id}')
        return True
    except Exception as e:
        print(f'{video_config["key"]} のアップロードに失敗しました: {str(e)}')
        return False

def main():
    # OSS 構成
    args = argparse.Namespace(
        region='cn-beijing',
        bucket='ipc-videos-oss-metaquery-demo',
        endpoint='https://oss-cn-beijing.aliyuncs.com'
    )

    # OSS クライアントの構成
    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    cfg.endpoint = args.endpoint
    client = oss.Client(cfg)

    # 動画ファイルの構成(異なるタグを含む)
    videos = [
        {
            'path': '<Your-Path>/VideoA.mp4',
            'key': 'VideoA.mp4'
        },
        {
            'path': '<Your-Path>/VideoB.mp4',
            'key': 'VideoB.mp4'
        },
        {
            'path': '<Your-Path>/VideoC.mp4',
            'key': 'VideoC.mp4'
        }
    ]

    # 全ての動画をアップロード
    for video in videos:
        print(f"\n{video['key']} のアップロードを開始します")
        upload_video(
            client=client,
            bucket=args.bucket,
            video_config=video
        )

if __name__ == "__main__":
    main()

実行前にプレースホルダーの値を置き換えてください。

プレースホルダー説明
<Your-Path>動画ファイルが格納されているローカルディレクトリ/home/user/videos

意味検索の実行

OSS MetaQuery をバックエンドサービスに統合し、プログラムから動画インデックスを照会します。すべての検索リクエストは、DoMetaQuery API を使用し、mode=semantic を指定します。

以下の例では、OSS MetaQuery の仕様に準拠した XML リクエストを構築し、署名付き URL を含む一致結果を返します:

# -*- coding: utf-8 -*-
import argparse
import alibabacloud_oss_v2 as oss
# XML 応答の解析
import xml.etree.ElementTree as ET
import json
from datetime import datetime

def get_search_conditions():
    """ユーザーからの複数条件入力の取得"""
    print("意味的な説明を入力してください(例:駐車中の車のある庭)")
    query = input("> 意味キーワード: ").strip()
    while not query:
        print("意味キーワードは空にできません!")
        query = input("> 意味キーワード: ").strip()
    return query

def build_metaquery_xml(query):
    """OSS 仕様に準拠した MetaQuery XML の構築"""
    xml_parts = [f'<Query>{query}</Query>']
    # MediaTypes タグを追加(ここではハードコーディングで video を指定)
    xml_parts.append('<MediaTypes><MediaType>video</MediaType></MediaTypes>')

    meta_query_xml = f'''<MetaQuery>
    {"".join(xml_parts)}
</MetaQuery>'''
    return meta_query_xml  # UTF-8 バイトストリームとしてエンコード

def format_result(key, pre_url):
    """単一の検索結果の整形"""
    return f""" ファイルパス: {key}
 ファイルアドレス: {pre_url}
-----------------------"""

def semantic_search():
    # コマンドライン引数に直接値を割り当て
    args = argparse.Namespace(
        region = 'cn-beijing',  # ご利用のリージョンに置き換え
        bucket = 'ipc-videos-oss-metaquery-demo',  # ご利用のバケット名に置き換え
        endpoint = 'https://oss-cn-beijing.aliyuncs.com',  # ご利用のエンドポイントに置き換え。不要な場合は空欄または削除可。
    )

    # OSS クライアントの初期化
    credentials = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials
    cfg.region = args.region
    if args.endpoint:
        cfg.endpoint = args.endpoint
    client = oss.Client(cfg)

    # ユーザー入力の取得
    query = get_search_conditions()  # クエリのみ取得
    # リクエストの構築
    try:
        # リクエストボディ(XML データ)の構築
        data_str = build_metaquery_xml(query)

        # 操作入力の定義
        req = oss.OperationInput(
            op_name='DoMetaQuery',  # カスタム操作名
            method='POST',            # HTTP メソッド
            parameters={              # クエリパラメーター
                'metaQuery': '',
                'mode': 'semantic',
                'comp': 'query',
            },
            headers=None,             # カスタムリクエストヘッダー(任意)
            body=data_str.encode("utf-8"),  # リクエストボディ(UTF-8 バイトストリームとしてエンコード)
            bucket=args.bucket,       # 対象バケット名
        )

        # 汎用インターフェイスを呼び出して操作を実行
        resp = client.invoke_operation(req)

    except oss.exceptions.ServiceError as e:
        print(f" サーバーエラー: {e.message}")
        return

    root = ET.fromstring(resp.http_response.content.decode('utf-8'))
     # すべての File 要素を検索
    files = root.findall('.//File')

    print(f"\n {len(files)} 件の一致結果が見つかりました:")



    for i, file in enumerate(files, 1):
        print(f"\nファイル {i}:")

        # さまざまなプロパティを取得して表示
        uri_element = file.find('URI')
        uri = uri_element.text if uri_element is not None else 'N/A'
        print(f"  URI: {uri}")

        key_element = file.find('Filename')
        key = key_element.text if key_element is not None else 'N/A'
        print(f"  ファイル名: {key}")

        size_element = file.find('Size')
        size = size_element.text if size_element is not None else 'N/A'
        print(f"  サイズ: {size}")

        modified_time_element = file.find('FileModifiedTime')
        modified_time = modified_time_element.text if modified_time_element is not None else 'N/A'
        print(f"  更新日時: {modified_time}")

        content_type_element = file.find('ContentType')
        content_type = content_type_element.text if content_type_element is not None else 'N/A'
        print(f"  ContentType: {content_type}")

        media_type_element = file.find('MediaType')
        media_type = media_type_element.text if media_type_element is not None else 'N/A'
        print(f"  MediaType: {media_type}")

        # その他のプロパティ(ImageHeight、ImageWidth、OSSStorageClass など)も必要に応じて取得・表示可能
        # image_height_element = file.find('ImageHeight')
        # image_height = image_height_element.text if image_height_element is not None else 'N/A'
        # print(f"  ImageHeight: {image_height}")

        # 署名付き URL の生成(必要に応じて)
        if key != 'N/A': # ファイル名が存在する場合のみ URL を生成
             try:
                pre_url = client.presign(
                    oss.GetObjectRequest(
                        bucket=args.bucket,  # バケット名を指定
                        key=key,        # オブジェクトキーを指定
                    )
                )
                print(f"  ファイルアドレス(署名付き URL): {pre_url.url}")
             except Exception as e:
                 print(f"  署名付き URL の生成に失敗しました: {e}")


        print("-" * 20) # 区切り線

if __name__ == "__main__":
    semantic_search()

プログラムを実行した後、a yard with a parked car などの説明を入力します。システムはデータ インデックスをクエリし、一致する結果を返します。各結果の署名付き URL を使用して、ビデオに直接アクセスします。

出力例:

1 件の一致結果が見つかりました:

ファイル 1:
  URI: oss://ipc-videos-oss-metaquery-demo/VideoA.mp4
  ファイル名: VideoA.mp4
  サイズ: 2311252
  更新日時: 2025-05-23T17:38:10+08:00
  ContentType: video/mp4
  MediaType: video
  ファイルアドレス(署名付き URL): https://ipc-videos-oss-metaquery-demo.oss-cn-beijing.aliyuncs.com/VideoA.mp4?x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-date=20250523T094511Z&x-oss-expires=900&x-oss-credential=LTAI********************%2F20250523%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=0bf38092c42a179ff0e8334c8bea3fd92f5a78599038e816e2ed3e02755542af
--------------------

動画タグの管理

大規模な動画データセットを扱う際、ファイルパスだけではフィルタリングや分類に十分でないことが多くあります。OSS のオブジェクトタグ付け機能では、ファイルにキーと値のペア形式のメタデータをアタッチできます(例:どのカメラで撮影されたか、どの地理的ゾーンに属するか)。タグを活用することで、以下のような操作が可能になります。

  • キーと値のペアを用いて動画を動的に分類できます

  • カメラ ID や所在地など、特定のサブセットに検索結果を迅速に絞り込めます

  • タグが付与されたファイルにのみ適用されるインデックス作成ルールを設定できます

本チュートリアルで使用する 3 つのサンプル動画のタグ付け内容は以下のとおりです。

ファイル内容タグ
VideoA.mp4裏庭の映像camera=camera-a
VideoB.mp4販売エリアの映像camera=camera-b
VideoC.mp4裏庭の映像(VideoA と類似)camera=camera-c
VideoA.mp4VideoB.mp4VideoC.mp4
exampleexample (1)example
裏庭のビデオ。camera-a自動販売機のビデオ。camera-b裏庭のビデオ。内容は Video A に類似しており、camera-c

タグは、アップロード時に設定することも、アップロード後に追加・更新することもできます。

アップロード時にタグを設定

以下の例では、動画ファイルのアップロードとタグの設定を 1 回の操作で行っています:

import argparse
import alibabacloud_oss_v2 as oss
from alibabacloud_oss_v2.models import PutObjectRequest
import os

def upload_video_with_tags(client, bucket, video_config):
    """タグ付きで動画をアップロード"""
    try:
        # ファイルの存在確認
        if not os.path.exists(video_config['path']):
            print(f'ファイルが存在しません: {video_config["path"]}')
            return False

        # アップロード対象のオブジェクトを作成
        uploader = client.uploader()

        # タグ文字列を直接使用(キーと値がシンプルな文字列の場合、エンコーディングは不要)
        tagging_str = f"camera={video_config['camera_tag']}"

        # アップロードリクエストの実行 - タグを直接付与
        result = uploader.upload_file(
            filepath=video_config['path'],
            request=PutObjectRequest(
                bucket=bucket,
                key=video_config['key'],
                tagging=tagging_str
            )
        )

        print(f'{video_config["key"]} のアップロードが成功しました:')
        print(f'状態コード: {result.status_code},'
              f' リクエスト ID: {result.request_id}')
        print(f'追加されたタグ: {tagging_str}')
        return True
    except Exception as e:
        print(f'{video_config["key"]} のアップロードに失敗しました: {str(e)}')
        return False


def main():
    # OSS 構成
    args = argparse.Namespace(
        region='cn-beijing',
        bucket='ipc-videos-oss-metaquery-demo',
        endpoint='https://oss-cn-beijing.aliyuncs.com'
    )

    # OSS クライアントの構成
    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    cfg.endpoint = args.endpoint
    client = oss.Client(cfg)

    # 動画ファイルの構成(異なるタグを含む)
    videos = [
        {
            'path': '<Your-Path>/VideoA.mp4',
            'key': 'VideoA.mp4',
            'camera_tag': 'camera-a'
        },
        {
            'path': '<Your-Path>/VideoB.mp4',
            'key': 'VideoB.mp4',
            'camera_tag': 'camera-b'
        },
        {
            'path': '<Your-Path>/VideoC.mp4',
            'key': 'VideoC.mp4',
            'camera_tag': 'camera-c'
        }
    ]

    # 全ての動画をアップロードし、タグを追加
    for video in videos:
        print(f"\n{video['key']} のアップロードを開始します")
        upload_video_with_tags(
            client=client,
            bucket=args.bucket,
            video_config=video
        )


if __name__ == "__main__":
    main()

アップロード後にタグを更新

既にアップロード済みのファイルにタグを追加または変更するには、PutObjectTaggingRequest を使用します:

import argparse
import alibabacloud_oss_v2 as oss
from alibabacloud_oss_v2.models import GetObjectTaggingRequest, PutObjectTaggingRequest, Tagging, TagSet, Tag


def apply_tags_to_frame(client, bucket, frame_key, tags):
    """動画へのタグの追加"""
    try:
        # タグ付けオブジェクトの構築
        tagging = Tagging(
            version=1,
            tag_set=TagSet(tags=tags)
        )

        # タグ更新リクエストの作成
        put_tag_request = PutObjectTaggingRequest(
            bucket=bucket,
            key=frame_key,
            tagging=tagging
        )

        # オブジェクトのタグを更新
        result = client.put_object_tagging(put_tag_request)

        # タグを文字列に変換して表示
        tags_str = '&'.join([f"{tag.key}={tag.value}" for tag in tags])
        print(f"{frame_key} へのタグの追加が成功しました: {tags_str}")
        return True
    except Exception as e:
        print(f"タグの追加に失敗しました: {str(e)}")
        return False

def frame_tags():
    # OSS 構成
    args = argparse.Namespace(
        region='cn-beijing',
        frame_bucket='ipc-videos-oss-metaquery-demo',
        endpoint='https://oss-cn-beijing.aliyuncs.com'
    )

    # OSS クライアントの構成
    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    cfg.endpoint = args.endpoint
    client = oss.Client(cfg)

    # 動画ファイル名の構成
    videos = [
        'VideoA.mp4',
        'VideoB.mp4',
        'VideoC.mp4'
    ]

    # 各動画を処理
    for video_filename in videos:
        print(f"\n{video_filename} のタグ処理を開始します") # 抽出した元のファイル名を表示

        # ファイル名拡張子を除去(例:'VideoA.mp4' → 'VideoA')
        # サンプルコードでは、動画ファイル名の最後の文字をカメラ識別子として抽出しています。これは参考用です。実際の業務要件に応じてタグを追加してください。
        video_name_base = video_filename.split('.')[0] if '.' in video_filename else video_filename
        tags = [
            Tag(key='camera', value=f'camera-{video_name_base[-1].lower()}' if video_name_base else 'camera-unknown'),
            # Tag(key='category', value='video_monitoring')       # 実際の業務カテゴリ情報を追加
        ]

        # 動画にタグを追加
        apply_tags_to_frame(client, args.frame_bucket, video_filename, tags)

        print(f"{video_filename} のタグ処理が完了しました\n") # 抽出した元のファイル名を表示

if __name__ == "__main__":
    frame_tags()

タグフィルターによる検索

意味検索では、説明に一致するすべての動画が返されます。結果をさらに絞り込むには(例:特定のカメラで撮影された裏庭の映像のみを取得)、初期検索結果が返された後にタグフィルターを適用します。

以下の例では、意味検索とクライアント側でのタグフィルターを組み合わせています:

# -*- coding: utf-8 -*-
import argparse
import alibabacloud_oss_v2 as oss
# XML 応答の解析
import xml.etree.ElementTree as ET
import json
from datetime import datetime
# --- 新規インポート ---
from alibabacloud_oss_v2.models import GetObjectTaggingRequest
# --- インポート終了 ---

def get_inputs():
    """ユーザーの意味クエリ(必須)およびフィルター条件(任意)の取得"""
    # 1. 必須の意味クエリの取得
    print(" 意味的な説明を入力してください(例:駐車中の車のある庭)")
    query = input("> 意味キーワード: ").strip()
    while not query:
        print(" 意味キーワードは空にできません!")
        query = input("> 意味キーワード: ").strip()

    conditions = [] # フィルター条件のリストを初期化
    target_tag = None # --- ターゲットタグを格納するために使用 ---

    # 2. 任意のタグフィルターの取得
    print("\n (任意)タグフィルター条件を入力してください(クライアント側フィルターに使用。例:camera=camera-a。スキップする場合は Enter を押してください)")
    tag_input = input("> タグ(key=value 形式): ").strip()
    if tag_input:
        if '=' in tag_input:
            # conditions.append({
            #     'field': 'Tags',
            #     'value': tag_input,
            #     'op': 'eq'
            # })
            target_tag = tag_input
            print(f" 情報:結果取得後に、'{target_tag}' タグを用いたクライアント側フィルターが適用されます。")
        else:
             print(" タグの形式が正しくありません。'key=value' 形式で入力してください。")

    # --- target_tag を返す ---
    return query, conditions, target_tag

def build_metaquery_xml(query, conditions):
    """OSS 仕様に準拠した MetaQuery XML の構築(意味クエリおよび任意のフィルターを含む)"""
    # 常に意味クエリ部分を含める
    xml_parts = [f'<Query>{query}</Query>']

    # メディアタイプの制約を追加
    xml_parts.append('<MediaTypes><MediaType>video</MediaType></MediaTypes>')

    # 任意のフィルター条件を追加 - 意味モードでは一部のフィールドのみサポート(ここでは Filename を例示)
    for cond in conditions:
        # 意味モードでは Tags フィールドの SimpleQuery は構築しない(現在は conditions に Tags を追加していないが、将来的なために残す)
        if cond['field'] == 'Tags':
            continue

        json_query = json.dumps({
            "Field": cond['field'],
            "Value": cond['value'],
            "Operation": cond['op']
        }, ensure_ascii=False)
        xml_parts.append(f'<SimpleQuery>{json_query}</SimpleQuery>')

    # 完全な MetaQuery XML に結合
    meta_query_xml = f'''<MetaQuery>
    {"".join(xml_parts)}
</MetaQuery>'''
    return meta_query_xml

def format_result(key, pre_url):
    """単一の検索結果の整形"""
    return f""" ファイルアドレス: {pre_url}
 ファイルパス: {key}
-----------------------"""

def perform_search():
    # コマンドライン引数に直接値を割り当て
    args = argparse.Namespace(
        region='cn-beijing',  # ご利用のリージョンに置き換え
        bucket='ipc-videos-oss-metaquery-demo',  # ご利用のバケット名に置き換え
        endpoint='https://oss-cn-beijing.aliyuncs.com',  # ご利用のエンドポイントに置き換え
    )

    # OSS クライアントの初期化
    credentials = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials
    cfg.region = args.region
    if args.endpoint:
        cfg.endpoint = args.endpoint
    client = oss.Client(cfg)

    # ユーザー入力(意味クエリ + 任意の条件 + ターゲットタグ)の取得
    query, conditions, target_tag = get_inputs()

    # リクエストの構築
    try:
        # リクエストボディ(XML データ)の構築
        # --- Tags を含まない conditions のみを渡す ---
        data_str = build_metaquery_xml(query, conditions)

        # 操作入力の定義
        req = oss.OperationInput(
            op_name='DoMetaQuery',
            method='POST',
            parameters={
                'metaQuery': '',
                'mode': 'semantic', # <-- 意味モードを再指定
                'comp': 'query',
            },
            headers=None,
            body=data_str.encode("utf-8"),
            bucket=args.bucket,
        )

        # 汎用インターフェイスを呼び出して操作を実行
        print("\n DoMetaQuery リクエストを送信中...")
        resp = client.invoke_operation(req)
        print(f"\n リクエストが成功しました。HTTP ステータスコード: {resp.http_response.status_code}")
    except oss.exceptions.ServiceError as e:
        print(f" サーバーエラー(ServiceError): {e.message}")
        print(f"   - HTTP ステータスコード: {e.status_code}")
        print(f"   - エラーコード: {e.error_code}")
        print(f"   - リクエスト ID: {e.request_id}")
        return
    except oss.exceptions.ClientError as e: # クライアントエラーまたはネットワークエラーのキャッチ
        print(f" クライアントまたはネットワークエラー(ClientError): {e.message}")
        return
    except Exception as e: # その他の可能性のある例外をキャッチ
        print(f" 予期しないエラー: {e}")
        import traceback
        traceback.print_exc() # トレースバックを完全に出力
        return


    # 結果の解析および処理...
    final_results_count = 0 # --- 新規:最終的な一致結果数をカウントするカウンター ---
    try:
        root = ET.fromstring(resp.http_response.content.decode('utf-8'))
        # すべての File 要素を検索
        files = root.findall('.//File')

        print(f"\n OSS から {len(files)} 件の初期一致結果が得られました。クライアント側タグフィルターを開始します...")

        if not files:
            # NextContinuationToken の有無を確認し、ページングが必要な場合がある
            next_token_elem = root.find('.//NextContinuationToken')
            if next_token_elem is not None and next_token_elem.text:
                print(" 注意:さらに結果がある可能性があります。現在の実装ではページングを処理していません。")
            print("\n 初期一致結果はありません。")
            return # ファイルがない場合は早期リターン

        for i, file in enumerate(files, 1):
            # ファイル名の取得
            key_element = file.find('Filename')
            if key_element is None:
                print(f" 警告:{i} 番目の初期結果に Filename フィールドがありません。スキップします。")
                continue
            key = key_element.text

            # --- クライアント側タグフィルター ---
            if target_tag:
                try:
                    tagging_req = GetObjectTaggingRequest(bucket=args.bucket, key=key)
                    tagging_resp = client.get_object_tagging(tagging_req)
                    # 返されたタグセットにターゲットタグが含まれているか確認
                    tag_found = False
                    target_k, target_v = target_tag.split('=', 1)

                    if tagging_resp.tag_set and tagging_resp.tag_set.tags: # .tags を使用
                        for tag in tagging_resp.tag_set.tags: # .tags を使用

                            if tag.key == target_k and tag.value == target_v:
                                tag_found = True
                                break
                    if not tag_found:
                        continue # タグが一致しないため、このファイルをスキップ
                except oss.exceptions.ServiceError as tag_err:
                     if tag_err.status_code == 404 and tag_err.error_code == 'NoSuchTagSet':
                         continue
                     else:
                        print(f" 警告:ファイル '{key}' のタグ取得中にエラーが発生しました: {tag_err.error_code} - {tag_err.message}。スキップします。")
                        continue
                except Exception as tag_e:
                    print(f" 警告:ファイル '{key}' のタグ取得または処理中に予期しないエラーが発生しました: {tag_e}。スキップします。")
                    # --- デバッグ用にトレースバックを追加 ---
                    import traceback
                    traceback.print_exc()
                    # --- 追加終了 ---
                    continue
            # --- クライアント側タグフィルター終了 ---

            # --- タグフィルターを通過した(またはタグフィルターが未設定)場合、結果を処理して表示 ---
            final_results_count += 1 # 最終結果カウンターをインクリメント
            print(f"\n[{final_results_count}] ファイル '{key}' がすべての条件に一致しました:") # 最終結果番号を表示

            # 署名付き URL の生成
            try:
                pre_url = client.presign(
                    oss.GetObjectRequest(
                        bucket=args.bucket,
                        key=key,
                    )
                )
                print(format_result(key, pre_url.url))
            except Exception as presign_e:
                print(f" 警告:ファイル '{key}' の署名付き URL 生成中にエラーが発生しました: {presign_e}")
                print(format_result(key, "[URL を生成できませんでした]"))
        # --- ループ終了後に最終統計を表示 ---
        print(f"\n クライアント側フィルター処理が完了しました。最終的な一致結果は {final_results_count} 件です。")

    except ET.ParseError as xml_e:
        print(f" エラー:OSS 応答 XML の解析エラー - {xml_e}")
    except Exception as parse_e:
        print(f" エラー:結果処理中に予期しないエラーが発生しました - {parse_e}")


if __name__ == "__main__":
    perform_search()

特定のカメラで撮影された裏庭の映像をフィルターするには、プログラムを実行し、以下の内容を入力します。

  1. 意味説明: a yard with a parked car

  2. タグフィルター: camera=camera-a

この例では、VideoA と VideoC の両方に説明に一致するシーンが含まれています。タグフィルターにより、VideoA(camera-a というタグが付与)のみが残り、最終結果には 1 件のファイルが含まれます。

出力例:

DoMetaQuery リクエストを送信中...

リクエストが成功しました。HTTP ステータスコード: 200

OSS から 2 件の初期一致結果が得られました。クライアント側タグフィルターを開始します...

[1] ファイル 'VideoA.mp4' がすべての条件に一致しました:
 ファイルアドレス: https://ipc-videos-oss-metaquery-demo.oss-cn-beijing.aliyuncs.com/VideoA.mp4?x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-date=20250526T054908Z&x-oss-expires=900&x-oss-credential=LTAI********************%2Fcn-beijing%2Foss%2Faliyun_v4_request&x-oss-signature=01bbf29790763d8e0f177d4cb0469cb00ae1c69d565219edb3866f75110b37ab
 ファイルパス: VideoA.mp4
-----------------------

クライアント側フィルター処理が完了しました。最終的な一致結果は 1 件です。