Skip to content

Commit

Permalink
Initial implementation of speech partial (#875)
Browse files Browse the repository at this point in the history
* Initial implementation of speech partial

* Update clients and add tests

* Add description to SpeechHelpersTrait

* Update comments

* Updated speech partial

* Update tests, fix bugs

* Add snippet tests, fix sample errors

* Revert changes in gapic file

* Updates to speech partial veneer

* Remove unused function

* Update after discussion about partials

* Address PR comments

* Make createAudioStreamFromResource public
  • Loading branch information
michaelbausor authored and dwsupplee committed May 1, 2018
1 parent e4e5503 commit eb55c9b
Show file tree
Hide file tree
Showing 8 changed files with 804 additions and 4 deletions.
117 changes: 117 additions & 0 deletions Speech/src/SpeechHelpersTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php
/**
* Copyright 2018 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Speech;

use Generator;
use InvalidArgumentException;

/**
* Provides helper functions for generated Speech clients.
*/
trait SpeechHelpersTrait
{
/**
* A list of allowed url schemes.
*
* @var array
*/
private static $urlSchemes = [
'gs'
];

/**
* @param string $recognitionAudioClass
* @param resource|string|RecognitionAudio $audio
* @return mixed A RecognitionAudio instance matching the requested version of Speech
*/
private function createRecognitionAudioHelper($recognitionAudioClass, $audio)
{
if ($audio instanceof $recognitionAudioClass) {
return $audio;
}
$recognitionAudio = new $recognitionAudioClass();
if (is_string($audio)) {
if (in_array(parse_url($audio, PHP_URL_SCHEME), self::$urlSchemes)) {
$recognitionAudio->setUri($audio);
} else {
$recognitionAudio->setContent($audio);
}
} elseif (is_resource($audio)) {
$recognitionAudio->setContent(stream_get_contents($audio));
} else {
throw new InvalidArgumentException(
'Given $audio is not valid. ' .
'Audio must be a RecognitionAudio ' .
'object, a string of bytes, a valid ' .
'Google Cloud Storage URI, or a resource.'
);
}
return $recognitionAudio;
}

/**
* @param string $requestClass
* @param iterable|resource|string $audio
* @return mixed An iterable of StreamingRecognizeRequest instances matching the requested version of Speech
*/
private function createStreamingRequestsHelper($requestClass, $audio)
{
// First, convert string/resource audio into an iterable
if (is_string($audio)) {
$audio = [$audio];
}
if (is_resource($audio)) {
$audio = $this->createAudioStreamFromResource($audio);
}

// For each chuck in iterable $audio, convert to a request if necessary
foreach ($audio as $audioChunk) {
if (is_object($audioChunk) && $audioChunk instanceof $requestClass) {
yield $audioChunk;
} elseif (is_string($audioChunk)) {
$request = new $requestClass();
$request->setAudioContent($audioChunk);
yield $request;
} else {
throw new InvalidArgumentException(
'Found invalid audio chunk in $audio. ' .
'Audio must be a resource, a string of ' .
'bytes, or an iterable of StreamingRecognizeRequest[] ' .
'or string[].'
);
}
}
}

/**
* Convert a PHP resource instance into an iterable of data "chunks".
*
* @param resource $resource The resource object to read data from.
* @param int $chunkSize The chunk size to use, in bytes. Defaults to 32000
* @return Generator<string> An iterable of strings that have been read from the resource.
*/
public function createAudioStreamFromResource($resource, $chunkSize = 32000)
{
while (!feof($resource)) {
$chunk = fread($resource, $chunkSize);
if (strlen($chunk) > 0) {
yield $chunk;
}
}
}
}
75 changes: 73 additions & 2 deletions Speech/src/V1/SpeechClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,84 @@

namespace Google\Cloud\Speech\V1;

use Google\Cloud\Speech\SpeechHelpersTrait;
use Google\Cloud\Speech\V1\Gapic\SpeechGapicClient;

/**
* {@inheritdoc}
*/
class SpeechClient extends SpeechGapicClient
{
// This class is intentionally empty, and is intended to hold manual
// additions to the generated {@see SpeechClientImpl} class.
use SpeechHelpersTrait;

/**
* Helper method to create a RecognitionAudio object from audio data.
*
* @param resource|string|RecognitionAudio $audio *Required* The audio data to be recognized. This can be a RecognitionAudio
* object, a Google Cloud Storage URI, a resource object, or a string of bytes.
* @return RecognitionAudio
*/
public function createRecognitionAudio($audio)
{
return $this->createRecognitionAudioHelper(RecognitionAudio::class, $audio);
}

/**
* Helper method to create a stream of StreamingRecognizeRequest objects from audio data.
*
* @param iterable|resource|string $audio *Required* The audio data to be converted into a stream of requests. This
* can be a resource, a string of bytes, or an iterable of
* StreamingRecognizeRequest[] or string[].
* @return StreamingRecognizeRequest[]
*/
public function createStreamingRequests($audio)
{
return $this->createStreamingRequestsHelper(StreamingRecognizeRequest::class, $audio);
}

/**
* Performs speech recognition on a stream of audio data. This method is only available via
* the gRPC API (not REST).
*
* Example:
* ```
* use Google\Cloud\Speech\V1\RecognitionConfig_AudioEncoding;
* use Google\Cloud\Speech\V1\RecognitionConfig;
* use Google\Cloud\Speech\V1\StreamingRecognitionConfig;
*
* $recognitionConfig = new RecognitionConfig();
* $recognitionConfig->setEncoding(RecognitionConfig_AudioEncoding::FLAC);
* $recognitionConfig->setSampleRateHertz(44100);
* $recognitionConfig->setLanguageCode('en-US');
* $config = new StreamingRecognitionConfig();
* $config->setConfig($recognitionConfig);
*
* $audioResource = fopen('path/to/audio.flac', 'r');
*
* $responses = $speechClient->recognizeAudioStream($config, $audioResource);
*
* foreach ($responses as $element) {
* // doSomethingWith($element);
* }
* ```
*
* @param StreamingRecognitionConfig $config *Required* Provides information to the recognizer that specifies how to
* process the request.
* @param iterable|resource|string $audio *Required* Audio data to be streamed. Can be a resource, a string of bytes,
* or an iterable of StreamingRecognizeRequest[] or string[].
* @param array $optionalArgs {
* Optional.
*
* @type int $timeoutMillis
* Timeout to use for this call.
* }
* @return StreamingRecognizeResponse[]
*/
public function recognizeAudioStream($config, $audio, $optionalArgs = [])
{
$bidiStream = $this->streamingRecognize($optionalArgs);
$bidiStream->write((new StreamingRecognizeRequest())->setStreamingConfig($config));
$bidiStream->writeAll($this->createStreamingRequestsHelper(StreamingRecognizeRequest::class, $audio));
return $bidiStream->closeWriteAndReadAll();
}
}
75 changes: 73 additions & 2 deletions Speech/src/V1beta1/SpeechClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,84 @@

namespace Google\Cloud\Speech\V1beta1;

use Google\Cloud\Speech\SpeechHelpersTrait;
use Google\Cloud\Speech\V1beta1\Gapic\SpeechGapicClient;

/**
* {@inheritdoc}
*/
class SpeechClient extends SpeechGapicClient
{
// This class is intentionally empty, and is intended to hold manual
// additions to the generated {@see SpeechClientImpl} class.
use SpeechHelpersTrait;

/**
* Helper method to create a RecognitionAudio object from audio data.
*
* @param resource|string|RecognitionAudio $audio *Required* The audio data to be recognized. This can be a RecognitionAudio
* object, a Google Cloud Storage URI, a resource object, or a string of bytes.
* @return RecognitionAudio
*/
public function createRecognitionAudio($audio)
{
return $this->createRecognitionAudioHelper(RecognitionAudio::class, $audio);
}

/**
* Helper method to create a stream of StreamingRecognizeRequest objects from audio data.
*
* @param iterable|resource|string $audio *Required* The audio data to be converted into a stream of requests. This
* can be a resource, a string of bytes, or an iterable of
* StreamingRecognizeRequest[] or string[].
* @return StreamingRecognizeRequest[]
*/
public function createStreamingRequests($audio)
{
return $this->createStreamingRequestsHelper(StreamingRecognizeRequest::class, $audio);
}

/**
* Performs speech recognition on a stream of audio data. This method is only available via
* the gRPC API (not REST).
*
* Example:
* ```
* use Google\Cloud\Speech\V1beta1\RecognitionConfig_AudioEncoding;
* use Google\Cloud\Speech\V1beta1\RecognitionConfig;
* use Google\Cloud\Speech\V1beta1\StreamingRecognitionConfig;
*
* $recognitionConfig = new RecognitionConfig();
* $recognitionConfig->setEncoding(RecognitionConfig_AudioEncoding::FLAC);
* $recognitionConfig->setSampleRate(44100);
* $recognitionConfig->setLanguageCode('en-US');
* $config = new StreamingRecognitionConfig();
* $config->setConfig($recognitionConfig);
*
* $audioResource = fopen('path/to/audio.flac', 'r');
*
* $responses = $speechClient->recognizeAudioStream($config, $audioResource);
*
* foreach ($responses as $element) {
* // doSomethingWith($element);
* }
* ```
*
* @param StreamingRecognitionConfig $config *Required* Provides information to the recognizer that specifies how to
* process the request.
* @param iterable|resource|string $audio *Required* Audio data to be streamed. Can be a resource, a string of bytes,
* or an iterable of StreamingRecognizeRequest[] or string[].
* @param array $optionalArgs {
* Optional.
*
* @type int $timeoutMillis
* Timeout to use for this call.
* }
* @return StreamingRecognizeResponse[]
*/
public function recognizeAudioStream($config, $audio, $optionalArgs = [])
{
$bidiStream = $this->streamingRecognize($optionalArgs);
$bidiStream->write((new StreamingRecognizeRequest())->setStreamingConfig($config));
$bidiStream->writeAll($this->createStreamingRequestsHelper(StreamingRecognizeRequest::class, $audio));
return $bidiStream->closeWriteAndReadAll();
}
}
73 changes: 73 additions & 0 deletions Speech/tests/Snippet/V1/SpeechClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
/**
* Copyright 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Tests\Snippets\Speech\V1;

use Google\ApiCore\BidiStream;
use Google\ApiCore\Call;
use Google\ApiCore\Testing\MockBidiStreamingCall;
use Google\ApiCore\Transport\TransportInterface;
use Google\Cloud\Core\Testing\Snippet\SnippetTestCase;
use Google\Cloud\Speech\V1\SpeechClient;
use Google\Cloud\Speech\V1\StreamingRecognizeResponse;
use Prophecy\Argument;

/**
* @group speech
*/
class SpeechClientTest extends SnippetTestCase
{
private $client;
private $transport;

public function setUp()
{
$this->transport = $this->prophesize(TransportInterface::class);
$this->client = new SpeechClient([
'transport' => $this->transport->reveal(),
]);
}

public function testRecognizeAudioStream()
{
$snippet = $this->snippetFromMethod(SpeechClient::class, 'recognizeAudioStream');
$snippet->addLocal('speechClient', $this->client);

$snippet->replace(
"path/to/audio.flac",
"php://temp"
);

$expectedResponseStream = [
new StreamingRecognizeResponse(),
new StreamingRecognizeResponse(),
];

$mockBidiStreamingCall = new MockBidiStreamingCall($expectedResponseStream);

$this->transport->startBidiStreamingCall(Argument::allOf(
Argument::type(Call::class),
Argument::which('getMethod', 'google.cloud.speech.v1.Speech/StreamingRecognize')
),
Argument::type('array')
)
->shouldBeCalledTimes(1)
->willReturn(new BidiStream($mockBidiStreamingCall));

$res = $snippet->invoke();
}
}
Loading

0 comments on commit eb55c9b

Please sign in to comment.