Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sample.content handle bytes and files. #2850

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions speech/google/cloud/speech/_gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,7 @@ def streaming_recognize(self, sample, language_code=None,
.cloud_speech_pb2.StreamingRecognizeResponse`
:returns: ``StreamingRecognizeResponse`` instances.
"""
if getattr(sample.content, 'closed', None) is None:
raise ValueError('Please use file-like object for data stream.')
if sample.content.closed:
if sample.stream.closed:
raise ValueError('Stream is closed.')

This comment was marked as spam.


requests = _stream_requests(sample, language_code=language_code,
Expand Down Expand Up @@ -252,7 +250,6 @@ def sync_recognize(self, sample, language_code=None, max_alternatives=None,
language_code=language_code, max_alternatives=max_alternatives,
profanity_filter=profanity_filter,
speech_context=SpeechContext(phrases=speech_context))

audio = RecognitionAudio(content=sample.content,
uri=sample.source_uri)
api = self._gapic_api
Expand Down Expand Up @@ -337,7 +334,7 @@ def _stream_requests(sample, language_code=None, max_alternatives=None,
yield config_request

while True:
data = sample.content.read(sample.chunk_size)
data = sample.stream.read(sample.chunk_size)
if not data:
break
yield StreamingRecognizeRequest(audio_content=data)
Expand Down
9 changes: 6 additions & 3 deletions speech/google/cloud/speech/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ def __init__(self, credentials=None, http=None, use_gax=None):
else:
self._use_gax = use_gax

def sample(self, content=None, source_uri=None, encoding=None,
def sample(self, content=None, source_uri=None, stream=None, encoding=None,
sample_rate=None):
"""Factory: construct Sample to use when making recognize requests.

:type content: bytes
:param content: (Optional) Byte stream of audio.
:param content: (Optional) Bytes containing audio data.

:type source_uri: str
:param source_uri: (Optional) URI that points to a file that contains
Expand All @@ -80,6 +80,9 @@ def sample(self, content=None, source_uri=None, encoding=None,
supported, which must be specified in the following
format: ``gs://bucket_name/object_name``.

:type stream: file
:param stream: (Optional) File like object to stream.

:type encoding: str
:param encoding: encoding of audio data sent in all RecognitionAudio
messages, can be one of: :attr:`~.Encoding.LINEAR16`,
Expand All @@ -97,7 +100,7 @@ def sample(self, content=None, source_uri=None, encoding=None,
:rtype: :class:`~google.cloud.speech.sample.Sample`
:returns: Instance of ``Sample``.
"""
return Sample(content=content, source_uri=source_uri,
return Sample(content=content, source_uri=source_uri, stream=stream,
encoding=encoding, sample_rate=sample_rate, client=self)

@property
Expand Down
27 changes: 21 additions & 6 deletions speech/google/cloud/speech/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Sample class to handle content for Google Cloud Speech API."""


from google.cloud.speech.encoding import Encoding
from google.cloud.speech.result import StreamingSpeechResult

Expand All @@ -22,7 +23,7 @@ class Sample(object):
"""Representation of an audio sample to be used with Google Speech API.

:type content: bytes
:param content: (Optional) Byte stream of audio.
:param content: (Optional) Bytes containing audio data.

:type source_uri: str
:param source_uri: (Optional) URI that points to a file that contains
Expand All @@ -31,6 +32,9 @@ class Sample(object):
supported, which must be specified in the following
format: ``gs://bucket_name/object_name``.

:type stream: file
:param stream: (Optional) File like object to stream.

:type encoding: str
:param encoding: encoding of audio data sent in all RecognitionAudio
messages, can be one of: :attr:`~.Encoding.LINEAR16`,
Expand All @@ -51,17 +55,19 @@ class Sample(object):
default_encoding = Encoding.FLAC
default_sample_rate = 16000

def __init__(self, content=None, source_uri=None,
def __init__(self, content=None, source_uri=None, stream=None,
encoding=None, sample_rate=None, client=None):
self._client = client

no_source = content is None and source_uri is None
both_source = content is not None and source_uri is not None
if no_source or both_source:
raise ValueError('Supply one of \'content\' or \'source_uri\'')
sources = [content is not None, source_uri is not None,
stream is not None]
if sources.count(True) != 1:

This comment was marked as spam.

This comment was marked as spam.

raise ValueError('Supply exactly one of '
'\'content\', \'source_uri\', \'stream\'')

self._content = content
self._source_uri = source_uri
self._stream = stream

if sample_rate is not None and not 8000 <= sample_rate <= 48000:
raise ValueError('The value of sample_rate must be between 8000'
Expand Down Expand Up @@ -109,6 +115,15 @@ def sample_rate(self):
"""
return self._sample_rate

@property
def stream(self):
"""Stream the content when it is a file-like object.

:rtype: file
:returns: File like object to stream.
"""
return self._stream

This comment was marked as spam.


@property
def encoding(self):
"""Audio encoding type
Expand Down
46 changes: 1 addition & 45 deletions speech/unit_tests/test__gax.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,6 @@

import unittest

import mock


def _make_credentials():
import google.auth.credentials
return mock.Mock(spec=google.auth.credentials.Credentials)


class TestGAPICSpeechAPI(unittest.TestCase):
SAMPLE_RATE = 16000

@staticmethod
def _get_target_class():
from google.cloud.speech._gax import GAPICSpeechAPI

return GAPICSpeechAPI

def _make_one(self, *args, **kw):
return self._get_target_class()(*args, **kw)

def test_use_bytes_instead_of_file_like_object(self):
from google.cloud import speech
from google.cloud.speech.sample import Sample

credentials = _make_credentials()
client = speech.Client(credentials=credentials, use_gax=True)
client.connection = _Connection()
client.connection.credentials = credentials

sample = Sample(content=b'', encoding=speech.Encoding.FLAC,
sample_rate=self.SAMPLE_RATE)

api = self._make_one(client)
with self.assertRaises(ValueError):
api.streaming_recognize(sample)
self.assertEqual(client.connection._requested, [])


class TestSpeechGAXMakeRequests(unittest.TestCase):
SAMPLE_RATE = 16000
Expand Down Expand Up @@ -143,7 +106,7 @@ def test_stream_requests(self):
from google.cloud.grpc.speech.v1beta1.cloud_speech_pb2 import (
StreamingRecognizeRequest)

sample = Sample(content=BytesIO(self.AUDIO_CONTENT),
sample = Sample(stream=BytesIO(self.AUDIO_CONTENT),
encoding=speech.Encoding.FLAC,
sample_rate=self.SAMPLE_RATE)
language_code = 'US-en'
Expand Down Expand Up @@ -172,10 +135,3 @@ def test_stream_requests(self):
self.assertEqual(streaming_request.audio_content, self.AUDIO_CONTENT)
self.assertIsInstance(config_request.streaming_config,
StreamingRecognitionConfig)


class _Connection(object):

def __init__(self, *responses):
self._responses = responses
self._requested = []
18 changes: 8 additions & 10 deletions speech/unit_tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class TestClient(unittest.TestCase):
SAMPLE_RATE = 16000
HINTS = ['hi']
AUDIO_SOURCE_URI = 'gs://sample-bucket/sample-recording.flac'
AUDIO_CONTENT = '/9j/4QNURXhpZgAASUkq'
AUDIO_CONTENT = b'testing 1 2 3'

@staticmethod
def _get_target_class():
Expand Down Expand Up @@ -125,14 +125,12 @@ def test_sync_recognize_content_with_optional_params_no_gax(self):
from base64 import b64encode

from google.cloud._helpers import _bytes_to_unicode
from google.cloud._helpers import _to_bytes

from google.cloud import speech
from google.cloud.speech.alternative import Alternative
from unit_tests._fixtures import SYNC_RECOGNIZE_RESPONSE

_AUDIO_CONTENT = _to_bytes(self.AUDIO_CONTENT)
_B64_AUDIO_CONTENT = _bytes_to_unicode(b64encode(_AUDIO_CONTENT))
_B64_AUDIO_CONTENT = _bytes_to_unicode(b64encode(self.AUDIO_CONTENT))
RETURNED = SYNC_RECOGNIZE_RESPONSE
REQUEST = {
'config': {
Expand Down Expand Up @@ -325,8 +323,7 @@ def speech_api(channel=None):
self.assertIsInstance(low_level, _MockGAPICSpeechAPI)
self.assertIs(low_level._channel, channel_obj)
self.assertEqual(
channel_args,
[(creds, _gax.DEFAULT_USER_AGENT, host)])
channel_args, [(creds, _gax.DEFAULT_USER_AGENT, host)])

results = sample.sync_recognize()

Expand Down Expand Up @@ -462,8 +459,9 @@ def speech_api(channel=None):
speech_api.SERVICE_ADDRESS = host

stream.close()
self.assertTrue(stream.closed)

sample = client.sample(content=stream,
sample = client.sample(stream=stream,
encoding=Encoding.LINEAR16,
sample_rate=self.SAMPLE_RATE)

Expand Down Expand Up @@ -523,7 +521,7 @@ def speech_api(channel=None):
make_secure_channel=make_channel):
client._speech_api = _gax.GAPICSpeechAPI(client)

sample = client.sample(content=stream,
sample = client.sample(stream=stream,
encoding=Encoding.LINEAR16,
sample_rate=self.SAMPLE_RATE)

Expand Down Expand Up @@ -596,7 +594,7 @@ def speech_api(channel=None):
make_secure_channel=make_channel):
client._speech_api = _gax.GAPICSpeechAPI(client)

sample = client.sample(content=stream,
sample = client.sample(stream=stream,
encoding=Encoding.LINEAR16,
sample_rate=self.SAMPLE_RATE)

Expand Down Expand Up @@ -640,7 +638,7 @@ def speech_api(channel=None):
make_secure_channel=make_channel):
client._speech_api = _gax.GAPICSpeechAPI(client)

sample = client.sample(content=stream,
sample = client.sample(stream=stream,
encoding=Encoding.LINEAR16,
sample_rate=self.SAMPLE_RATE)

Expand Down
34 changes: 34 additions & 0 deletions speech/unit_tests/test_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,44 @@ def test_initialize_sample(self):
self.assertEqual(sample.sample_rate, self.SAMPLE_RATE)

def test_content_and_source_uri(self):
from io import BytesIO

with self.assertRaises(ValueError):
self._make_one(content='awefawagaeragere',
source_uri=self.AUDIO_SOURCE_URI)

with self.assertRaises(ValueError):
self._make_one(stream=BytesIO(b'awefawagaeragere'),
source_uri=self.AUDIO_SOURCE_URI)

with self.assertRaises(ValueError):
self._make_one(content='awefawagaeragere',
stream=BytesIO(b'awefawagaeragere'),
source_uri=self.AUDIO_SOURCE_URI)

def test_stream_property(self):
from io import BytesIO
from google.cloud.speech.encoding import Encoding

data = b'abc 1 2 3 4'
stream = BytesIO(data)
sample = self._make_one(stream=stream, encoding=Encoding.FLAC,
sample_rate=self.SAMPLE_RATE)
self.assertEqual(sample.stream, stream)
self.assertEqual(sample.stream.read(), data)

def test_bytes_converts_to_file_like_object(self):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

from google.cloud import speech
from google.cloud.speech.sample import Sample

test_bytes = b'testing 1 2 3'

sample = Sample(content=test_bytes, encoding=speech.Encoding.FLAC,
sample_rate=self.SAMPLE_RATE)
self.assertEqual(sample.content, test_bytes)
self.assertEqual(sample.encoding, speech.Encoding.FLAC)
self.assertEqual(sample.sample_rate, self.SAMPLE_RATE)

def test_sample_rates(self):
from google.cloud.speech.encoding import Encoding

Expand Down
2 changes: 1 addition & 1 deletion system_tests/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def _make_async_request(self, content=None, source_uri=None,
def _make_streaming_request(self, file_obj, single_utterance=True,
interim_results=False):
client = Config.CLIENT
sample = client.sample(content=file_obj,
sample = client.sample(stream=file_obj,
encoding=speech.Encoding.LINEAR16,
sample_rate=16000)
return sample.streaming_recognize(single_utterance=single_utterance,
Expand Down