使用Rev AI和PHP构建语音到文本的Web应用程序(第2部分)
#教程 #php #api #speechrecognition

Vikram Vaswani,开发人员倡导者
本教程最初于2022年9月8日在https://docs.rev.ai/resources/tutorials/build-speech-to-text-web-application-php-2/发表。

介绍

Rev AI的automatic speech recognition (ASR) APIs使开发人员能够将快速准确的语音到文本功能集成到其应用中。本教程的第一部分向您介绍了Rev AI的Asynchronous Speech-to-Text API。它解释了如何通过Web浏览器录制音频,并使用Guzlezle PHP HTTP客户端将音频提交给Rev AI进行转录。

first part of this tutorial末尾,示例应用程序能够向AI提交音频,但是您仍然必须使用Rev AI仪表板来手动检索您的成绩单。这个结论段关闭了循环,解释了如何使用Webhook从Rev AI检索成绩单并在示例应用程序中显示它们。它还将解释如何在Web应用程序中添加成绩单删除和搜索功能。

注意:complete source code for the example application is available on GitHub,因此您可以立即下载并尝试。

假设

本教程假设:

任何使用Rev AI API的应用程序也必须遵守Rev AI的API limitsterms of service。在继续之前,请查看这些文件并确保您与它们达成协议。

注意:本教程使用基于Docker的Apache/PHP/MongoDB开发环境。如果您已经与Apache 2.xPHP 8.1.xMongoDB extensionComposer具有正确配置的开发环境,则可以使用它。您可能需要用等效替换一些docker命令。

步骤1:使用Webhook从Rev AI接收成绩单

一旦将作业提交给Rev AI进行异步转录的Rev AI,就有三种方法可以知道何时完成转录。您可以通过Rev AI Web仪表板检查工作状态;您可以反复对API进行调查;或者,您可以让Rev AI通过Webhook自动通知您。在这三个选项中,使用Webhook是最有效的方法。

注意:如果您不熟悉Webhooks,请参阅我们在getting started with Rev AI webhooks上的教程,以获取更多信息。您还可以学习如何使用Rev AI Webhooks到automatically send email notifications when a job completes或如何使用retrieve final transcripts via the API and save them to a MongoDB database

让Rev AI知道它应该使用webhook,包括请求在https://api.rev.ai/speechtotext/v1/jobs上的异步语音到文本API端点的notification_config参数,如下所示:

curl -X POST "https://api.rev.ai/speechtotext/v1/jobs" \
     -H "Authorization: Bearer <REVAI_ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{
       "source_config": {"url": "https://www.rev.ai/FTC_Sample_1.mp3"},
       "notification_config": {"url": "https://example.com/my/webhook"}
      }'

作业完成后,API将向指定的URL提出HTTP POST请求,并在POST请求正文中使用JSON文档。这是邮政请求主体的示例:

{
  "job": {
    "id": "8xBckmk6rAqu",
    "created_on": "2022-08-16T14:26:14.151Z",
    "completed_on": "2022-08-16T14:26:53.601Z",
    "name": "FTC_Sample_1.mp3",
    "notification_config": {"url": "https://example.com/my/webhook"},
    "source_config": {"url": "https://www.rev.ai/FTC_Sample_1.mp3"},
    "status": "transcribed",
    "duration_seconds": 107,
    "type": "async",
    "language": "en"
  }
}

从上面的讨论中,应该清楚地表明,将Rev AI Webhook集成到应用程序中时涉及两个步骤:

  1. 在工作提交时间,在工作请求的notification_config参数中包括一个Webhook URL。
  2. 定义能够接收HTTP POST请求的Webhook URL处理程序,解析请求主体并根据收到的数据采取进一步的措施(例如检索成绩单)。

注意:为了从Rev AI接收通知,Webhook URL必须是公开访问的URL。在本地开发和测试时,这可能并非总是可能的。对于这种情况,请使用koude0创建一个临时公共URL,将用作Webhook URL。

进行更改如下所述:

  1. 使用Webhook URL的附加配置键更新config/settings.php文件。

    <?php
    return [
        'rev' => [
            'token' => '<REVAI_ACCESS_TOKEN>',
            'callback' => '<CALLBACK_PREFIX>/hook',
        ],
        'mongo' => [
            'uri' => '<MONGODB_URI>'
        ]
    ];
    

    如果您将应用程序部署在现有的公共URL中,请用应用程序URL替换<CALLBACK_PREFIX>占位符。

    如果您在本地开发和测试没有公开访问的URL,请首先使用ngrok创建并获得临时的Webhook URL,然后用该临时URL替换config/settings.php文件中的<CALLBACK_PREFIX>占位符。

  2. public\index.php上更新前控制器,并进行以下更改:

  • 更新/add URL端点的邮路处理程序以在作业请求中包括notification_config参数。

      <?php
    
      // ...
    
      // POST request handler for /add page
      $app->post(
          '/add',
          function (Request $request, Response $response) {
              // get MongoDB service
              // insert a record in the database for the audio upload
              // get MongoDB document ID
              $mongoClient = $this->get('mongo');
              try {
                  $insertResult = $mongoClient->mydb->notes->insertOne(
                      [
                          'status' => 'JOB_RECORDED',
                          'ts'     => time(),
                          'jid'    => false,
                          'error'  => false,
                          'data'   => false,
                      ]
                  );
                  $id           = (string) $insertResult->getInsertedId();
                  // get uploaded file
                  // if no upload errors, change status in database record
                  $uploadedFiles = $request->getUploadedFiles();
                  $uploadedFile = $uploadedFiles['file'];
                  if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
                      $mongoClient->mydb->notes->updateOne(
                          [
                              '_id' => new ObjectID($id),
                          ],
                          [
                              '$set' => ['status' => 'JOB_UPLOADED'],
                          ]
                      );
                      // get Rev AI API client
                      // submit audio to API as POST request
                      $revClient   = $this->get('guzzle');
                      $revResponse = $revClient->request(
                          'POST',
                          'jobs',
                          [
                              'multipart' => [
                                  [
                                      'name'     => 'media',
                                      'contents' => fopen($uploadedFile->getFilePath(), 'r'),
                                  ],
                                  [
                                      'name'     => 'options',
                                      'contents' => json_encode(
                                          [
                                              'metadata'         => $id,
                                              'skip_diarization' => 'true',
                                              'notification_config'     => [
                                                  'url' => $this->get('settings')['rev']['callback']
                                              ],
                                          ]
                                      ),
                                  ],
                              ],
                          ]
                      )->getBody()->getContents();
                      // get API response
                      // if no API error, update status in database record
                      // send 200 response code to client
                      $json        = json_decode($revResponse);
                      $mongoClient->mydb->notes->updateOne(
                          [
                              '_id' => new ObjectID($id),
                          ],
                          [
                              '$set' => [
                                  'status' => 'JOB_TRANSCRIPTION_IN_PROGRESS',
                                  'jid'    => $json->id,
                              ],
                          ]
                      );
                      $response->getBody()->write(json_encode(['success' => true]));
                      return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
                  }
              } catch (\GuzzleHttp\Exception\RequestException $e) {
                  // in case of API error
                  // update status in database record
                  // send error code to client with error message as payload
                  $mongoClient->mydb->notes->updateOne(
                      [
                          '_id' => new ObjectID($id),
                      ],
                      [
                          '$set' => [
                              'status' => 'JOB_TRANSCRIPTION_FAILURE',
                              'error'  => $e->getMessage(),
                          ],
                      ]
                  );
                  $response->getBody()->write(json_encode(['success' => false]));
                  return $response->withHeader('Content-Type', 'application/json')->withStatus($e->getResponse()->getStatusCode());
              }
          }
      );
    
      // ...
    
  • 将一个新的处理程序添加到/hook Webhook URL端点,该端点接受并处理从Rev AI API收到的邮政请求。

      <?php
    
      // ...
    
      // POST request handler for /hook webhook URL
      $app->post(
          '/hook',
          function (Request $request, Response $response) {
              try {
                  // get MongoDB service
                  $mongoClient = $this->get('mongo');
    
                  // decode JSON request body
                  // obtain identifiers and status
                  $json        = json_decode($request->getBody());
                  $jid         = $json->job->id;
                  $id          = $json->job->metadata;
    
                  // if job successful
                  if ($json->job->status === 'transcribed') {
    
                      // update status in database
                      $mongoClient->mydb->notes->updateOne(
                          [
                              '_id' => new ObjectID($id),
                          ],
                          [
                              '$set' => ['status' => 'JOB_TRANSCRIPTION_SUCCESS'],
                          ]
                      );
    
                      // get transcript from API
                      $revClient   = $this->get('guzzle');
                      $revResponse = $revClient->request(
                          'GET',
                          "jobs/$jid/transcript",
                          [
                              'headers' => ['Accept' => 'text/plain'],
                          ]
                      )->getBody()->getContents();
                      $transcript  = explode('    ', $revResponse)[2];
    
                      // save transcript to database
                      $mongoClient->mydb->notes->updateOne(
                          [
                              '_id' => new ObjectID($id),
                          ],
                          [
                              '$set' => ['data' => $transcript],
                          ]
                      );
                  // if job unsuccesful
                  } else {
    
                      // update status in database
                      // save problem detail error message
                      $mongoClient->mydb->notes->updateOne(
                          [
                              '_id' => new ObjectID($id),
                          ],
                          [
                              '$set' => [
                                  'status' => 'JOB_TRANSCRIPTION_FAILURE',
                                  'error'  => $json->job->failure_detail,
                              ],
                          ]
                      );
                  }
              } catch (\GuzzleHttp\Exception\RequestException $e) {
                  $mongoClient->mydb->notes->updateOne(
                      [
                        '_id' => new ObjectID($id),
                      ],
                      [
                        '$set' => [
                            'status' => 'JOB_TRANSCRIPTION_FAILURE',
                            'error'  => $e->getMessage(),
                        ],
                      ]
                  );
              }
              return $response->withStatus(200);
          }
      );
    
      // ...
    

此路由处理程序包含很多代码,所以让我们浏览它:

当使用HTTP POST请求调用此端点时,处理程序首先检查收到的JSON文档并从中提取三个关键信息:

  • Rev AI作业标识符,将用于检索最终成绩单;
  • MongoDB文档标识符,该标识符用于标识应用程序数据库中的相应记录。回忆起,该作业最初提交时,该MongoDB文档标识符包括在metadata参数中;
  • Rev AI工作状态,指示转录是成功还是失败。

如果工作成功,则处理程序将文档status更新为JOB_TRANSCRIPTION_SUCCESS。然后,它使用Guzlezz Rev AI API客户端来准备并将HTTP获取请求发送到https://api.rev.ai/speechtotext/v1/jobs/<ID>/transcript,以以明文格式检索最终成绩单。然后更新MongoDB文档,并将成绩单内容保存到文档的data字段中。

如果工作不成功,则处理程序将文档status更新为JOB_TRANSCRIPTION_FAILURE。在这种情况下,JSON文档还包括一个问题描述,该文档将保存在MongoDB数据库中的文档的error字段中。

完成上述操作后,处理程序将200响应代码返回到Rev AI API服务器。

步骤2:列出应用程序中的成绩单

一旦webhook到位,检索并将成绩单保存到mongoDB数据库,下一步就是在应用程序用户界面中显示它们。

  1. 更新/index端点的Get Route处理程序,以执行MongoDB查询并返回应用程序数据库中的所有记录。在public/index.php的前控制器中进行此更改。

    <?php
    // ...
    
    // GET request handler for index page
    $app->get(
        '/[index[/]]',
        function (Request $request, Response $response, $args) {
            $params = $request->getQueryParams();
            $mongoClient = $this->get('mongo');
            return $this->get('view')->render(
                $response,
                'index.twig',
                [
                    'status' => !empty($params['status']) ? $params['status'] : null,
                    'data'   => $mongoClient->mydb->notes->find(
                        [],
                        [
                            'sort' => [
                                'ts' => -1,
                            ],
                        ]
                    )
                ]
            );
        }
    )->setName('index');
    
    // ...
    

    MongoDB结果集通过data Twig模板变量返回。

  2. 更新public/index.twig页面模板以循环到结果集,并将MongoDB结果集作为HTML表,包括成绩单内容,时间戳,状态和Rev AI工作标识符。作为此更新的一部分,在页面上添加“刷新”按钮,该按钮为用户重新加载页面提供了一种方法。

    {% extends "layout.twig" %}
    
    {% block content %}
      <header class="d-flex justify-content-center py-3">
        <h1>My Notes</h1>
      </header>
    
      {% if status == 'submitted' %}
      <div class="alert alert-success text-center" role="alert">Audio sent for transcription.</div>
      {% endif %}
    
      {% if status == 'error' %}
      <div class="alert alert-danger text-center" role="alert">Audio transcription failed.</div>
      {% endif %}
    
      <table class="table">
        <thead class="thead-light">
          <tr>
            <th scope="col">#</th>
            <th scope="col">Date</th>
            <th scope="col">Contents</th>
            <th scope="col">Status</th>
            <th scope="col">Rev AI Job ID</th>
            <th scope="col"><a class="btn btn-primary" href="{{ url_for('index') }}" role="button">Refresh</a></th>
          </tr>
        </thead>
        <tbody>
      {% for item in data %}
        <tr>
          <td>{{ loop.index }}</td>
          <td>{{ item.ts|date("d M Y h:i e") }}</td>
          <td class="text-wrap" style="max-width: 150px;">{{ item.data }}</td>
        {% if item.status in ['JOB_UPLOADED', 'JOB_TRANSCRIPTION_IN_PROGRESS'] %}
          <td>In progress</td>
        {% elseif item.status == 'JOB_RECORDED' %}
          <td>Recorded</td>
        {% elseif item.status == 'JOB_TRANSCRIPTION_SUCCESS' %}
          <td>Transcribed</td>
        {% elseif item.status == 'JOB_TRANSCRIPTION_FAILURE' %}
          <td class="text-wrap" style="max-width: 150px;">Failed <br/> {{ item.error }}</td>
        {% endif %}
          <td>{{ item.jid }}</td>
          <td></td>
        </tr>
      {% endfor %}
        </tbody>
      </table>
    {% endblock %}
    

    此模板循环在data模板变量上,对于每个记录,将显示格式的日期和时间,成绩单数据,作业状态和Rev AI作业标识符。请注意,该模板检查文档的status字段,这可能是JOB_RECORDEDJOB_UPLOADEDJOB_TRANSCRIPTION_IN_PROGRESSJOB_TRANSCRIPTION_IN_PROGRESSJOB_TRANSCRIPTION_SUCCESSJOB_TRANSCRIPTION_FAILURE的任何一个,并显示每个人的可读消息,包括失败的作业的错误消息。

步骤3:在应用程序中搜索成绩单

现在您已经拥有基础知识了,现在该为应用程序添加进一步的功能了。例如,用户应该能够快速搜索生成的成绩单并找到那些匹配的特定术语。

这可以通过使用mongoDB正则表达式来过滤存储的成绩单并返回匹配的书本,如下所述:

  1. views/index.twig上更新索引页模板以包含一个简单的搜索表格,如下:

    {% extends "layout.twig" %}
    
    {% block content %}
    
      <!-- ... -->
    
      <div class="col-sm-4">
        <form class="form-inline">
          <div class="input-group">
            <input class="form-control" name="term" value="{{ term }}" placeholder="Search">
            <div class="input-group-append">
              <button class="btn btn-outline-secondary" type="submit">Go</button>
            </div>
          </div>
        </form>
      </div>
    
      <!-- ... -->
    
    {% endblock %}
    

    当提交此搜索表格时,用户输入的搜索词将作为查询参数添加到URL中。

  2. 更新前控制器脚本中的/index端点的Get Route处理程序public/index.php读取提交的搜索词,并使用附加的正则表达式过滤器修改默认的mongoDB查询。此更改可确保仅返回匹配搜索词的结果并显示在索引页模板中。

    <?php
    // ...
    
    // GET request handler for index page
    $app->get(
        '/[index[/]]',
        function (Request $request, Response $response, $args) {
            $params = $request->getQueryParams();
            $condition = !empty($params['term']) ?
                [
                    'data' => new MongoDB\BSON\Regex(filter_var($params['term'], FILTER_UNSAFE_RAW), 'i')
                ] :
                [];
            $mongoClient = $this->get('mongo');
            return $this->get('view')->render(
                $response,
                'index.twig',
                [
                    'status' => !empty($params['status']) ? $params['status'] : null,
                    'data'   => $mongoClient->mydb->notes->find(
                        $condition,
                        [
                            'sort' => [
                                'ts' => -1,
                            ],
                        ]
                    ),
                    'term' => !empty($params['term']) ? $params['term'] : null,
                ]
            );
        }
    )->setName('index');
    
    // ...
    

    路由处理程序使用请求对象的getQueryParams()方法检索搜索词,然后将正则表达条件添加到默认的mongoDB搜索查询中。此条件可确保仅通过查询返回包含正则表达式的成绩单。然后将结果集插入到索引页模板中以照常显示。

步骤4:从应用程序删除成绩单

用户还应该选择删除不再通过Web应用程序接口相关的语音注释。

实现以下功能:

  1. views/index.twig上更新索引页模板,以支持每个显示的成绩单和一个新的deleted状态消息,如下所示。

    {% extends "layout.twig" %}
    
    {% block content %}
    
      <!-- ... -->
    
      {% if status == 'deleted' %}
      <div class="alert alert-success text-center" role="alert">Note deleted.</div>
      {% endif %}
    
      <!-- ... -->
    
      <table class="table">
    
        <!-- ... -->
    
        <tbody>
      {% for item in data %}
        <tr>
          <td>{{ loop.index }}</td>
          <td>{{ item.ts|date("d M Y h:i e") }}</td>
          <td class="text-wrap" style="max-width: 150px;">{{ item.data }}</td>
        {% if item.status in ['JOB_UPLOADED', 'JOB_TRANSCRIPTION_IN_PROGRESS'] %}
          <td>In progress</td>
        {% elseif item.status == 'JOB_RECORDED' %}
          <td>Recorded</td>
        {% elseif item.status == 'JOB_TRANSCRIPTION_SUCCESS' %}
          <td>Transcribed</td>
        {% elseif item.status == 'JOB_TRANSCRIPTION_FAILURE' %}
          <td class="text-wrap" style="max-width: 150px;">Failed <br/> {{ item.error }}</td>
        {% endif %}
          <td>{{ item.jid }}</td>
          <td><a class="btn btn-danger" href="{{ url_for('delete', { 'id': item._id }) }}" role="button">Delete</a></td>
        </tr>
      {% endfor %}
        </tbody>
      </table>
    {% endblock %}
    

    请注意,每个“删除”按钮的生成的超链接将指向一个名为delete的路由,并将MongoDB文档标识符包括为URL参数。

  2. 为新的/delete端点创建一个Get Route处理程序,该端点接受MongoDB文档标识符作为URL参数。将此路线添加到前控制器脚本public/index.php

    <?php
    
    // ...
    
    // GET request handler for /delete page
    $app->get(
        '/delete/{id}',
        function (Request $request, Response $response, $args) use ($app) {
            $id          = filter_var($args['id'], FILTER_UNSAFE_RAW);
            $mongoClient = $this->get('mongo');
            $mongoClient->mydb->notes->deleteOne(
                [
                    '_id' => new ObjectID($id),
                ]
            );
            $routeParser = $app->getRouteCollector()->getRouteParser();
            return $response->withHeader('Location', $routeParser->urlFor('index', [], ['status' => 'deleted']))->withStatus(302);
        }
    )->setName('delete');
    
    // ...
    

    此路由处理程序读取MongoDB文档标识符,使用MongoDB客户端的deleteOne方法从应用程序数据库中删除相应的成绩单,然后将用户重定向回到/index url,并获得成功通知。

    >

    >

注意:删除本节所述的语音注释将其从应用程序数据库中删除,但不会从Rev AI的服务器中删除。为此,您必须通过API提交单独的DELETE请求,或在初始作业请求中包含delete_after_seconds参数。要了解更多信息,请参阅Rev AI中删除用户数据的教程。

供参考,这是最终的前控制器脚本。用此版本替换public\index.php文件。

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Slim\Routing\RouteContext;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use DI\ContainerBuilder;
use MongoDB\BSON\ObjectID;

// load dependencies
require __DIR__ . '/https://docs.rev.ai/resources/tutorials/vendor/autoload.php';

// create DI container
$containerBuilder = new ContainerBuilder();

// define services
$containerBuilder->addDefinitions(
    [
        'settings' => function () {
            return include __DIR__ . '/https://docs.rev.ai/resources/tutorials/config/settings.php';
        },
        'view'     => function () {
            return Twig::create(__DIR__ . '/https://docs.rev.ai/resources/tutorials/views');
        },
        'mongo'    => function ($c) {
            return new MongoDB\Client($c->get('settings')['mongo']['uri']);
        },
        'guzzle'   => function ($c) {
            $token = $c->get('settings')['rev']['token'];
            return new Client(
                [
                    'base_uri' => 'https://api.rev.ai/speechtotext/v1/jobs',
                    'headers'  => ['Authorization' => "Bearer $token"],
                ]
            );
        },
    ]
);

$container = $containerBuilder->build();

AppFactory::setContainer($container);

// create application with DI container
$app = AppFactory::create();

// add Twig middleware
$app->add(TwigMiddleware::createFromContainer($app));

// add error handling middleware
$app->addErrorMiddleware(true, true, true);

// GET request handler for index page
$app->get(
  '/[index[/]]',
  function (Request $request, Response $response, $args) {
      $params = $request->getQueryParams();
      $condition = !empty($params['term']) ?
          [
              'data' => new MongoDB\BSON\Regex(filter_var($params['term'], FILTER_UNSAFE_RAW), 'i')
          ] :
          [];
      $mongoClient = $this->get('mongo');
      return $this->get('view')->render(
          $response,
          'index.twig',
          [
              'status' => !empty($params['status']) ? $params['status'] : null,
              'data'   => $mongoClient->mydb->notes->find(
                  $condition,
                  [
                      'sort' => [
                          'ts' => -1,
                      ],
                  ]
              ),
              'term' => !empty($params['term']) ? $params['term'] : null,
          ]
      );
  }
)->setName('index');

// GET request handler for /add page
$app->get(
    '/add',
    function (Request $request, Response $response, $args) {
        return $this->get('view')->render(
            $response,
            'add.twig',
            []
        );
    }
)->setName('add');

// POST request handler for /add page
$app->post(
    '/add',
    function (Request $request, Response $response) {
        // get MongoDB service
        // insert a record in the database for the audio upload
        // get MongoDB document ID
        $mongoClient = $this->get('mongo');
        try {
            $insertResult = $mongoClient->mydb->notes->insertOne(
                [
                    'status' => 'JOB_RECORDED',
                    'ts'     => time(),
                    'jid'    => false,
                    'error'  => false,
                    'data'   => false,
                ]
            );
            $id           = (string) $insertResult->getInsertedId();

            // get uploaded file
            // if no upload errors, change status in database record
            $uploadedFiles = $request->getUploadedFiles();
            $uploadedFile = $uploadedFiles['file'];

            if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
                $mongoClient->mydb->notes->updateOne(
                    [
                        '_id' => new ObjectID($id),
                    ],
                    [
                        '$set' => ['status' => 'JOB_UPLOADED'],
                    ]
                );

                // get Rev AI API client
                // submit audio to API as POST request
                $revClient   = $this->get('guzzle');
                $revResponse = $revClient->request(
                    'POST',
                    'jobs',
                    [
                        'multipart' => [
                            [
                                'name'     => 'media',
                                'contents' => fopen($uploadedFile->getFilePath(), 'r'),
                            ],
                            [
                                'name'     => 'options',
                                'contents' => json_encode(
                                    [
                                        'metadata'         => $id,
                                        'notification_config'     => [
                                            'url' => $this->get('settings')['rev']['callback']
                                        ],
                                        'skip_diarization' => 'true',
                                    ]
                                ),
                            ],
                        ],
                    ]
                )->getBody()->getContents();

                // get API response
                // if no API error, update status in database record
                // send 200 response code to client
                $json        = json_decode($revResponse);
                $mongoClient->mydb->notes->updateOne(
                    [
                        '_id' => new ObjectID($id),
                    ],
                    [
                        '$set' => [
                            'status' => 'JOB_TRANSCRIPTION_IN_PROGRESS',
                            'jid'    => $json->id,
                        ],
                    ]
                );
                $response->getBody()->write(json_encode(['success' => true]));
                return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
            }
        } catch (\GuzzleHttp\Exception\RequestException $e) {
            // in case of API error
            // update status in database record
            // send error code to client with error message as payload
            $mongoClient->mydb->notes->updateOne(
                [
                    '_id' => new ObjectID($id),
                ],
                [
                    '$set' => [
                        'status' => 'JOB_TRANSCRIPTION_FAILURE',
                        'error'  => $e->getMessage(),
                    ],
                ]
            );
            $response->getBody()->write(json_encode(['success' => false]));
            return $response->withHeader('Content-Type', 'application/json')->withStatus($e->getResponse()->getStatusCode());
        }
    }
);

// GET request handler for /delete page
$app->get(
    '/delete/{id}',
    function (Request $request, Response $response, $args) use ($app) {
        $id          = filter_var($args['id'], FILTER_UNSAFE_RAW);
        $mongoClient = $this->get('mongo');
        $mongoClient->mydb->notes->deleteOne(
            [
                '_id' => new ObjectID($id),
            ]
        );
        $routeParser = $app->getRouteCollector()->getRouteParser();
        return $response->withHeader('Location', $routeParser->urlFor('index', [], ['status' => 'deleted']))->withStatus(302);
    }
)->setName('delete');

// POST request handler for /hook webhook URL
$app->post(
    '/hook',
    function (Request $request, Response $response) {
        try {
            // get MongoDB service
            $mongoClient = $this->get('mongo');

            // decode JSON request body
            // obtain identifiers and status
            $json        = json_decode($request->getBody());
            $jid         = $json->job->id;
            $id          = $json->job->metadata;

            // if job successful
            if ($json->job->status === 'transcribed') {

                // update status in database
                $mongoClient->mydb->notes->updateOne(
                    [
                        '_id' => new ObjectID($id),
                    ],
                    [
                        '$set' => ['status' => 'JOB_TRANSCRIPTION_SUCCESS'],
                    ]
                );

                // get transcript from API
                $revClient   = $this->get('guzzle');
                $revResponse = $revClient->request(
                    'GET',
                    "jobs/$jid/transcript",
                    [
                        'headers' => ['Accept' => 'text/plain'],
                    ]
                )->getBody()->getContents();
                $transcript  = explode('    ', $revResponse)[2];

                // save transcript to database
                $mongoClient->mydb->notes->updateOne(
                    [
                        '_id' => new ObjectID($id),
                    ],
                    [
                        '$set' => ['data' => $transcript],
                    ]
                );
            // if job unsuccesful
            } else {

                // update status in database
                // save problem detail error message
                $mongoClient->mydb->notes->updateOne(
                    [
                        '_id' => new ObjectID($id),
                    ],
                    [
                        '$set' => [
                            'status' => 'JOB_TRANSCRIPTION_FAILURE',
                            'error'  => $json->job->failure_detail,
                        ],
                    ]
                );
            }
        } catch (\GuzzleHttp\Exception\RequestException $e) {
            $mongoClient->mydb->notes->updateOne(
                [
                  '_id' => new ObjectID($id),
                ],
                [
                  '$set' => [
                      'status' => 'JOB_TRANSCRIPTION_FAILURE',
                      'error'  => $e->getMessage(),
                  ],
                ]
            );
        }
        return $response->withStatus(200);
    }
);

$app->run();

步骤5:测试示例应用程序

通过浏览http://<DOCKER_HOST>来测试示例应用程序。您应该看到下面的页面。

Image description

单击右上角的“添加”按钮。您将被重定向到新页面,浏览器将提示访问系统麦克风。授予此权限,然后单击“开始录制”按钮。说话并单击完成后单击“停止录制”。您的音频将被上传,您应该重定向到索引页面,您应该在其中看到列出的语音注释,并带有“正在进行中的状态”。

Image description

成绩单准备就绪时,Rev AI将通知Webhook URL。如果您使用的是公共URL,则可以在Web服务器日志中看到此活动。另外,如果您使用的是ngrok,则可以在ngrok仪表板中看到此活动,如下所示:

Image description

当您在Web服务器日志或ngrok监视器中查看传入的Webhook请求,或者大约1分钟后,如果您无法访问监视,请单击“刷新”按钮。语音笔记列表现在应反映更新的状态以及成绩单,如下所示:

Image description

如果由于错误而无法生成笔录,则该列表也应在列表中看到:

Image description

在搜索框中输入搜索词,然后单击“ GO”按钮。现在,应过滤语音笔记列表以显示包含您的搜索词的语音列表,如下所示:

Image description

单击语音笔记旁边的“删除”按钮。该注释将从数据库中删除,并将从可用注释的列表中消失。

下一步

在这个结论段中,您了解了如何使用Webhook从Rev AI检索最终成绩单并将其显示在Web应用程序中。您还使用基本搜索和删除功能改进了Web应用程序。

这样,示例语音到文本Web应用程序已完成。通过浏览器收到和记录音频,并通过Rev AI和Php生成,返回并集成到应用程序中。

通过访问以下链接,了解有关使用Rev AI和PHP开发语音到文本应用程序的更多信息: