使用 .NET 创建视频审查Create video reviews using .NET

本文提供了信息和代码示例,帮助你快速开始结合使用内容审查器 SDK 和 C# 来执行以下操作:This article provides information and code samples to help you quickly get started using the Content Moderator SDK with C# to:

  • 为人工审查器创建视频评论Create a video review for human moderators
  • 将帧添加到审查Add frames to a review
  • 获取帧以供审查Get the frames for the review
  • 获取审查状态和详细信息Get the status and details of the review
  • 发布评论Publish the review

先决条件Prerequisites

确保 API 密钥可以调用评审 API 以创建评审Ensure your API key can call the review API for review creation

完成上述步骤后,如果从 Azure 门户着手,最终可能会得到两个内容审查器密钥。After completing the previous steps, you may end up with two Content Moderator keys if you started from the Azure portal.

如果计划在 SDK 示例中使用 Azure 提供的 API 密钥,请按照将 Azure 密钥与评审 API 配合使用部分中提到的步骤操作,以允许应用程序调用评审 API 并创建评审。If you plan to use the Azure-provided API key in your SDK sample, follow the steps mentioned in the Using Azure key with the review API section to allow your application to call the review API and create reviews.

如果使用评审工具生成的免费试用密钥,则评审工具帐户已经知道密钥,因此无需其他步骤。If you use the free trial key generated by the review tool, your review tool account already knows about the key and therefore, no additional steps are required.

准备视频和视频帧以供审查Prepare your video and the video frames for review

必须在线发布要审查的视频和示例视频帧,因为你需要它们的 URL。The video and sample video frames to review must be published online because you need their URLs.

备注

程序使用带随机成人/不雅分数的视频中手动保存的屏幕截图显示审查 API 的使用。The program uses manually saved screenshots from the video with random adult/racy scores to illustrate the use of the review API. 在实际情况下,使用视频审查输出创建图像并分配分数。In a real-world situation, you use the video moderation output to create images and assign scores.

对于视频,需要一个流式处理终结点,以便审查工具在播放器视图中播放此视频。For the video, you need a streaming endpoint so that the review tool plays the video in the player view.

视频演示缩略图

对于视频帧(图像),使用下列图像:For the video frames (images), use the following images:

视频帧缩略图 1 视频帧缩略图 2 视频帧缩略图 3
帧 1Frame 1 帧 2Frame 2 帧 3Frame 3

创建 Visual Studio 项目Create your Visual Studio project

  1. 向解决方案添加新的“控制台应用(.NET Framework)”**** 项目。Add a new Console app (.NET Framework) project to your solution.

  2. 为项目“VideoReviews”**** 命名。Name the project VideoReviews.

  3. 将此项目选为解决方案的单一启动项目。Select this project as the single startup project for the solution.

安装所需程序包Install required packages

为 TermLists 项目安装以下 NuGet 包。Install the following NuGet packages for the TermLists project.

  • Microsoft.Azure.CognitiveServices.ContentModeratorMicrosoft.Azure.CognitiveServices.ContentModerator
  • Microsoft.Rest.ClientRuntimeMicrosoft.Rest.ClientRuntime
  • Microsoft.Rest.ClientRuntime.AzureMicrosoft.Rest.ClientRuntime.Azure
  • Newtonsoft.JsonNewtonsoft.Json

更新程序的 using 语句Update the program's using statements

按下面所示修改程序的 using 语句。Modify the program's using statements as follows.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Microsoft.Azure.CognitiveServices.ContentModerator;
using Microsoft.Azure.CognitiveServices.ContentModerator.Models;
using Newtonsoft.Json;

添加私有属性Add private properties

将以下专用属性添加到 VideoReviews**** 命名空间中的 Program**** 类。Add the following private properties to namespace VideoReviews, class Program. 使用终结点 URL 和订阅密钥的值更新 AzureEndpointCMSubscriptionKey 字段。Update the AzureEndpoint and CMSubscriptionKey fields with the values of your endpoint URL and subscription key. 可在 Azure 门户中资源的“快速启动”**** 选项卡中找到它们。You can find these in the Quick start tab of your resource in the Azure portal.

namespace VideoReviews
{
    class Program
    {
        // NOTE: Enter a valid endpoint URL
        /// <summary>
        /// The endpoint URL of your subscription
        /// </summary>
        private static readonly string AzureEndpoint = "YOUR ENDPOINT URL";

        // NOTE: Enter a valid subscription key.
        /// <summary>
        /// Your Content Moderator subscription key.
        /// </summary>
        private static readonly string CMSubscriptionKey = "YOUR CONTENT MODERATOR KEY";

        // NOTE: Replace this example team name with your Content Moderator team name.
        /// <summary>
        /// The name of the team to assign the job to.
        /// </summary>
        /// <remarks>This must be the team name you used to create your 
        /// Content Moderator account. You can retrieve your team name from
        /// the Content Moderator web site. Your team name is the Id associated 
        /// with your subscription.</remarks>
        private const string TeamName = "YOUR CONTENT MODERATOR TEAM ID";

        /// <summary>
        /// The minimum amount of time, in milliseconds, to wait between calls
        /// to the Content Moderator APIs.
        /// </summary>
        private const int throttleRate = 2000;

创建内容审查器客户端对象Create Content Moderator Client object

将以下方法定义添加到 VideoReviews**** 命名空间中的 Program**** 类。Add the following method definition to namespace VideoReviews, class Program.

/// <summary>
/// Returns a new Content Moderator client for your subscription.
/// </summary>
/// <returns>The new client.</returns>
/// <remarks>The <see cref="ContentModeratorClient"/> is disposable.
/// When you have finished using the client,
/// you should dispose of it either directly or indirectly. </remarks>
public static ContentModeratorClient NewClient()
{
    return new ContentModeratorClient(new ApiKeyServiceClientCredentials(CMSubscriptionKey))
    {
        Endpoint = AzureEndpoint
    };
}

创建视频评论Create a video review

使用 ContentModeratorClient.Reviews.CreateVideoReviews 创建视频评论。Create a video review with ContentModeratorClient.Reviews.CreateVideoReviews. 有关详细信息,请参阅 API 参考For more information, see the API reference.

CreateVideoReviews 具有以下必需参数:CreateVideoReviews has the following required parameters:

  1. 一个字符串,包含应为“application/json”的 MIME 类型。A string that contains a MIME type, which should be "application/json."
  2. 内容审查器团队名称。Your Content Moderator team name.
  3. 一个 IList<CreateVideoReviewsBodyItem> 对象。An IList<CreateVideoReviewsBodyItem> object. 每个 CreateVideoReviewsBodyItem **** 对象表示一次视频审查。Each CreateVideoReviewsBodyItem object represents a video review. 本快速入门一次创建一条评论。This quickstart creates one review at a time.

CreateVideoReviewsBodyItem 具有多个属性。CreateVideoReviewsBodyItem has several properties. 至少应设置以下属性:At a minimum, you set the following properties:

  • Content****。Content. 要评论的视频的 URL。The URL of the video to be reviewed.
  • ContentId****。ContentId. 要分配给视频评论的 ID。An ID to assign to the video review.
  • Status****。Status. 将该值设置为“未发布”。Set the value to "Unpublished." 如果未进行设置,则默认为“挂起”,这意味着视频评论已发布并且正在等待人工评论。If you do not set it, it defaults to "Pending", which means the video review is published and pending human review. 视频评论发布后,就无法再向其中添加视频帧、脚本或脚本审查结果。Once a video review is published, you can no longer add video frames, a transcript, or a transcript moderation result to it.

备注

CreateVideoReviews 返回 IList<string>。CreateVideoReviews returns an IList<string>. 这些字符串中的每一个都包含视频评论 ID。Each of these strings contains an ID for a video review. 这些 ID 是 GUID,与 ContentId 属性的值不同。These IDs are GUIDs and are not the same as the value of the ContentId property.

将以下方法定义添加到 VideoReviews 命名空间中的 Program 类。Add the following method definition to namespace VideoReviews, class Program.

/// <summary>
/// Create a video review. For more information, see the API reference:
/// https://dev.cognitive.azure.cn/docs/services580519463f9b070e5c591178/operations/580519483f9b0709fc47f9c4 
/// </summary>
/// <param name="client">The Content Moderator client.</param>
/// <param name="id">The ID to assign to the video review.</param>
/// <param name="content">The URL of the video to review.</param>
/// <returns>The ID of the video review.</returns>
private static string CreateReview(ContentModeratorClient client, string id, string content)
{
    Console.WriteLine("Creating a video review.");

    List<CreateVideoReviewsBodyItem> body = new List<CreateVideoReviewsBodyItem>() {
        new CreateVideoReviewsBodyItem
        {
            Content = content,
            ContentId = id,
            /* Note: to create a published review, set the Status to "Pending".
            However, you cannot add video frames or a transcript to a published review. */
            Status = "Unpublished",
        }
    };

    var result = client.Reviews.CreateVideoReviews("application/json", TeamName, body);

    Thread.Sleep(throttleRate);

    // We created only one review.
    return result[0];
}

备注

内容审查器服务密钥有每秒请求数 (RPS) 速率限制。如果超出此限制,SDK 就会抛出异常(错误代码为 429)。Your Content Moderator service key has a requests per second (RPS) rate limit, and if you exceed the limit, the SDK throws an exception with a 429 error code.

免费层密钥有一个 RPS 速率限制。A free tier key has a one RPS rate limit.

将视频帧添加到视频审查Add video frames to the video review

使用“ContentModeratorClient.Reviews.AddVideoFrameUrl”****(如果在线托管视频帧)或“ContentModeratorClient.Reviews.AddVideoFrameStream”****(如果在本地托管视频帧)将视频帧添加到视频审查。You add video frames to a video review with ContentModeratorClient.Reviews.AddVideoFrameUrl (if your video frames are hosted online) or ContentModeratorClient.Reviews.AddVideoFrameStream (if your video frames are hosted locally). 本快速入门教程假定的是在线托管视频帧,因此使用“AddVideoFrameUrl”****。This quickstart assumes your video frames are hosted online, and so uses AddVideoFrameUrl. 有关详细信息,请参阅 API 参考For more information, see the API reference.

“AddVideoFrameUrl”**** 具有以下必需参数:AddVideoFrameUrl has the following required parameters:

  1. 一个字符串,包含应为“application/json”的 MIME 类型。A string that contains a MIME type, which should be "application/json."
  2. 内容审查器团队名称。Your Content Moderator team name.
  3. CreateVideoReviews 返回的视频评论 ID。The video review ID returned by CreateVideoReviews.
  4. 一个 IList<VideoFrameBodyItem> 对象。An IList<VideoFrameBodyItem> object. 每个“VideoFrameBodyItem”**** 对象表示一个视频帧。Each VideoFrameBodyItem object represents a video frame.

“VideoFrameBodyItem”**** 具有以下属性:VideoFrameBodyItem has the following properties:

  • TimestampTimestamp. 一个包含视频帧在视频中所用时间(以秒为单位)的字符串。A string that contains, in seconds, the time in the video from which the video frame was taken.
  • FrameImage****。FrameImage. 视频帧的 URL。The URL of the video frame.
  • Metadata****。Metadata. 一个 IList<VideoFrameBodyItemMetadataItem>。An IList<VideoFrameBodyItemMetadataItem>. “VideoFrameBodyItemMetadataItem”**** 只是一个键/值对。VideoFrameBodyItemMetadataItem is simply a key/value pair. 有效键包括:Valid keys include:
  • reviewRecommended****。reviewRecommended. 如果推荐视频帧的人工审查,则为 True。True if a human review of the video frame is recommended.
  • adultScore****。adultScore. 评估视频帧中成人内容严重性的值,范围从 0 到 1。A value from 0 to 1 that rates the severity of adult content in the video frame.
  • a****。a. 如果视频包含成人内容,则为 True。True if the video contains adult content.
  • racyScore****。racyScore. 评估视频帧中不雅内容严重性的值,范围从 0 到 1。A value from 0 to 1 that rates the severity of racy content in the video frame.
  • r****。r. 如果视频帧包含不雅内容,则为 True。True if the video frame contains racy content.
  • ReviewerResultTags****。ReviewerResultTags. 一个 IList<VideoFrameBodyItemReviewerResultTagsItem>。An IList<VideoFrameBodyItemReviewerResultTagsItem>. “VideoFrameBodyItemReviewerResultTagsItem”**** 只是一个键/值对。VideoFrameBodyItemReviewerResultTagsItem is simply a key/value pair. 应用程序可以使用这些标记来组织视频帧。An application can use these tags to organize video frames.

备注

本快速入门教程为“adultScore”**** 和“racyScore”**** 属性生成随机值。This quickstart generates random values for the adultScore and racyScore properties. 在生产应用程序中,将从视频审查服务中获取这些值,部署为 Azure 媒体服务。In a production application, you would obtain these values from the video moderation service, deployed as an Azure Media Service.

添加下列方法定义到命名空间 VideoReviews、类程序。Add the following method definitions to namespace VideoReviews, class Program.

<summary>
/// Create a video frame to add to a video review after the video review is created.
/// </summary>
/// <param name="url">The URL of the video frame image.</param>
/// <returns>The video frame.</returns>
private static VideoFrameBodyItem CreateFrameToAddToReview(string url, string timestamp_seconds)
{
    // We generate random "adult" and "racy" scores for the video frame.
    Random rand = new Random();

    var frame = new VideoFrameBodyItem
    {
        // The timestamp is measured in milliseconds. Convert from seconds.
        Timestamp = (int.Parse(timestamp_seconds) * 1000).ToString(),
        FrameImage = url,

        Metadata = new List<VideoFrameBodyItemMetadataItem>
        {
            new VideoFrameBodyItemMetadataItem("reviewRecommended", "true"),
            new VideoFrameBodyItemMetadataItem("adultScore", rand.NextDouble().ToString()),
            new VideoFrameBodyItemMetadataItem("a", "false"),
            new VideoFrameBodyItemMetadataItem("racyScore", rand.NextDouble().ToString()),
            new VideoFrameBodyItemMetadataItem("r", "false")
        },

        ReviewerResultTags = new List<VideoFrameBodyItemReviewerResultTagsItem>()
        {
            new VideoFrameBodyItemReviewerResultTagsItem("tag1", "value1")
        }
    };

    return frame;
}
/// <summary>
/// Add a video frame to the indicated video review. For more information, see the API reference:
/// https://chinaeast2.dev.cognitive.azure.cn/docs/services/580519463f9b070e5c591178/operations/59e7b76ae7151f0b10d451fd
/// </summary>
/// <param name="client">The Content Moderator client.</param>
/// <param name="review_id">The video review ID.</param>
/// <param name="url">The URL of the video frame image.</param>
static void AddFrame(ContentModeratorClient client, string review_id, string url, string timestamp_seconds)
{
    Console.WriteLine("Adding a frame to the review with ID {0}.", review_id);

    var frames = new List<VideoFrameBodyItem>()
    {
        CreateFrameToAddToReview(url, timestamp_seconds)
    };
        
    client.Reviews.AddVideoFrameUrl("application/json", TeamName, review_id, frames);

    Thread.Sleep(throttleRate);

为视频审查获取视频帧Get video frames for video review

可使用“ContentModeratorClient.Reviews.GetVideoFrames”**** 为视频审查获取视频帧。You can get the video frames for a video review with ContentModeratorClient.Reviews.GetVideoFrames. “GetVideoFrames”**** 具有以下必需参数:GetVideoFrames has the following required parameters:

  1. 内容审查器团队名称。Your Content Moderator team name.
  2. CreateVideoReviews 返回的视频评论 ID。The video review ID returned by CreateVideoReviews.
  3. 待获取的首个视频帧的从零开始的索引。The zero-based index of the first video frame to get.
  4. 待获取的视频帧的数字。The number of video frames to get.

将以下方法定义添加到 VideoReviews 命名空间中的 Program 类。Add the following method definition to namespace VideoReviews, class Program.

/// <summary>
/// Get the video frames assigned to the indicated video review.  For more information, see the API reference:
/// https://dev.cognitive.azure.cn/docs/services/580519463f9b070e5c591178/operations/59e7ba43e7151f0b10d45200
/// </summary>
/// <param name="client">The Content Moderator client.</param>
/// <param name="review_id">The video review ID.</param>
static void GetFrames(ContentModeratorClient client, string review_id)
{
    Console.WriteLine("Getting frames for the review with ID {0}.", review_id);

    Frames result = client.Reviews.GetVideoFrames(TeamName, review_id, 0);
    Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));

    Thread.Sleep(throttleRate);
}

获取视频审查信息Get video review information

使用“ContentModeratorClient.Reviews.GetReview”**** 获取视频审查信息。You get information for a video review with ContentModeratorClient.Reviews.GetReview. “GetReview”**** 具有以下必需参数:GetReview has the following required parameters:

  1. 内容审查器团队名称。Your Content Moderator team name.
  2. CreateVideoReviews 返回的视频评论 ID。The video review ID returned by CreateVideoReviews.

将以下方法定义添加到 VideoReviews 命名空间中的 Program 类。Add the following method definition to namespace VideoReviews, class Program.

/// <summary>
/// Get the information for the indicated video review. For more information, see the reference API:
/// https://dev.cognitive.azure.cn/docs/services/580519463f9b070e5c591178/operations/580519483f9b0709fc47f9c2
/// </summary>
/// <param name="client">The Content Moderator client.</param>
/// <param name="review_id">The video review ID.</param>
private static void GetReview(ContentModeratorClient client, string review_id)
{
    Console.WriteLine("Getting the status for the review with ID {0}.", review_id);

    var result = client.Reviews.GetReview(ModeratorHelper.Clients.TeamName, review_id);
    Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));

    Thread.Sleep(throttleRate);
}

发布视频评论Publish video review

使用 ContentModeratorClient.Reviews.PublishVideoReview 发布视频评论。You publish a video review with ContentModeratorClient.Reviews.PublishVideoReview. PublishVideoReview 具有以下必需参数:PublishVideoReview has the following required parameters:

  1. 内容审查器团队名称。Your Content Moderator team name.
  2. CreateVideoReviews 返回的视频评论 ID。The video review ID returned by CreateVideoReviews.

将以下方法定义添加到 VideoReviews 命名空间中的 Program 类。Add the following method definition to namespace VideoReviews, class Program.

/// <summary>
/// Publish the indicated video review. For more information, see the reference API:
/// https://dev.cognitive.azure.cn/docs/services/580519463f9b070e5c591178/operations/59e7bb29e7151f0b10d45201
/// </summary>
/// <param name="client">The Content Moderator client.</param>
/// <param name="review_id">The video review ID.</param>
private static void PublishReview(ContentModeratorClient client, string review_id)
{
    Console.WriteLine("Publishing the review with ID {0}.", review_id);
    client.Reviews.PublishVideoReview(TeamName, review_id);
    Thread.Sleep(throttleRate);
}

汇总Putting it all together

将“Main”**** 方法定义添加到命令空间 VideoReviews、类程序。Add the Main method definition to namespace VideoReviews, class Program. 最后,关闭程序类和 VideoReviews 命名空间。Finally, close the Program class and the VideoReviews namespace.

static void Main(string[] args)
{
    using (ContentModeratorClient client = NewClient())
    {
        // Create a review with the content pointing to a streaming endpoint (manifest)
        var streamingcontent = "https://amssamples.streaming.mediaservices.windows.net/91492735-c523-432b-ba01-faba6c2206a2/AzureMediaServicesPromo.ism/manifest";
        string review_id = CreateReview(client, "review1", streamingcontent);

        var frame1_url = "https://blobthebuilder.blob.core.windows.net/sampleframes/ams-video-frame1-00-17.PNG";
        var frame2_url = "https://blobthebuilder.blob.core.windows.net/sampleframes/ams-video-frame-2-01-04.PNG";
        var frame3_url = "https://blobthebuilder.blob.core.windows.net/sampleframes/ams-video-frame-3-02-24.PNG";

        // Add the frames from 17, 64, and 144 seconds.
        AddFrame(client, review_id, frame1_url, "17");
        AddFrame(client, review_id, frame2_url, "64");
        AddFrame(client, review_id, frame3_url, "144");

        // Get frames information and show
        GetFrames(client, review_id);
        GetReview(client, review_id);

        // Publish the review
        PublishReview(client, review_id);

        Console.WriteLine("Open your Content Moderator Dashboard and select Review > Video to see the review.");
        Console.WriteLine("Press any key to close the application.");
        Console.ReadKey();
    }
}

运行程序并查看输出Run the program and review the output

运行应用程序时,将显示以下行中的输出:When you run the application, you see an output on the following lines:

Creating a video review.
Adding a frame to the review with ID 201801v3212bda70ced4928b2cd7459c290c7dc.
Adding a frame to the review with ID 201801v3212bda70ced4928b2cd7459c290c7dc.
Adding a frame to the review with ID 201801v3212bda70ced4928b2cd7459c290c7dc.
Getting frames for the review with ID 201801v3212bda70ced4928b2cd7459c290c7dc.
{
    "ReviewId": "201801v3212bda70ced4928b2cd7459c290c7dc",
    "VideoFrames": [
    {
        "Timestamp": "17000",
        "FrameImage": "https://reviewcontentprod.blob.core.windows.net/testreview6/FRM_201801v3212bda70ced4928b2cd7459c290c7dc_17000.PNG",
        "Metadata": [
        {
            "Key": "reviewRecommended",
            "Value": "true"
        },
        {
            "Key": "adultScore",
            "Value": "0.808312381528463"
        },
        {
            "Key": "a",
            "Value": "false"
        },
        {
            "Key": "racyScore",
            "Value": "0.846378884206702"
        },
        {
            "Key": "r",
            "Value": "false"
        }
        ],
        "ReviewerResultTags": [
        {
            "Key": "tag1",
            "Value": "value1"
        }
    ]
    },
    {
        "Timestamp": "64000",
        "FrameImage": "https://reviewcontentprod.blob.core.windows.net/testreview6/FRM_201801v3212bda70ced4928b2cd7459c290c7dc_64000.PNG",
        "Metadata": [
        {
            "Key": "reviewRecommended",
            "Value": "true"
        },
        {
            "Key": "adultScore",
            "Value": "0.576078300166912"
        },
        {
            "Key": "a",
            "Value": "false"
        },
        {
            "Key": "racyScore",
            "Value": "0.244768953064815"
        },
        {
            "Key": "r",
            "Value": "false"
        }
        ],
        "ReviewerResultTags": [
        {
            "Key": "tag1",
            "Value": "value1"
        }
    ]
    },
    {
        "Timestamp": "144000",
        "FrameImage": "https://reviewcontentprod.blob.core.windows.net/testreview6/FRM_201801v3212bda70ced4928b2cd7459c290c7dc_144000.PNG",
        "Metadata": [
        {
            "Key": "reviewRecommended",
            "Value": "true"
        },
        {
            "Key": "adultScore",
            "Value": "0.664480847150311"
        },
        {
            "Key": "a",
            "Value": "false"
        },
        {
            "Key": "racyScore",
            "Value": "0.933817870418456"
        },
        {
            "Key": "r",
            "Value": "false"
        }
        ],
        "ReviewerResultTags": [
        {
            "Key": "tag1",
            "Value": "value1"
        }
        ]
    }
    ]
}

Getting the status for the review with ID 201801v3212bda70ced4928b2cd7459c290c7dc.
{
    "ReviewId": "201801v3212bda70ced4928b2cd7459c290c7dc",
    "SubTeam": "public",
    "Status": "UnPublished",
    "ReviewerResultTags": [],
    "CreatedBy": "testreview6",
    "Metadata": [
    {
        "Key": "FrameCount",
        "Value": "3"
    }
    ],
    "Type": "Video",
    "Content": "https://amssamples.streaming.mediaservices.windows.net/91492735-c523-432b-ba01-faba6c2206a2/AzureMediaServicesPromo.ism/manifest",
    "ContentId": "review1",
    "CallbackEndpoint": null
}

Publishing the review with ID 201801v3212bda70ced4928b2cd7459c290c7dc.
Open your Content Moderator Dashboard and select Review > Video to see the review.
Press any key to close the application.

查看视频审查Check out your video review

最后,你会在“审查”****>“视频”**** 屏幕上的内容审查器中看到视频审查。Finally, you see the video review in your Content Moderator review tool account on the Review>Video screen.

用于人工审查器的视频审查

后续步骤Next steps

为适用于 .NET 的此内容审查器快速入门以及其他内容审查器快速入门获取内容审查器 .NET SDKVisual Studio 解决方案Get the Content Moderator .NET SDK and the Visual Studio solution for this and other Content Moderator quickstarts for .NET.

了解如何将脚本审查添加到视频审查。Learn how to add transcript moderation to the video review.

查看有关如何开发完整视频审查解决方案的详细教程。Check out the detailed tutorial on how to develop a complete video moderation solution.