remove gitignore
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
from .asr_phrase_manager import AsrPhraseManager
|
||||
from .recognition import Recognition, RecognitionCallback, RecognitionResult
|
||||
from .transcription import Transcription
|
||||
|
||||
__all__ = [
|
||||
Transcription, Recognition, RecognitionCallback, RecognitionResult,
|
||||
AsrPhraseManager
|
||||
]
|
||||
@@ -0,0 +1,179 @@
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Dict
|
||||
|
||||
from dashscope.api_entities.dashscope_response import DashScopeAPIResponse
|
||||
from dashscope.client.base_api import BaseAsyncApi
|
||||
from dashscope.common.error import InvalidParameter
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.finetune import FineTune
|
||||
|
||||
|
||||
class AsrPhraseManager(BaseAsyncApi):
|
||||
"""Hot word management for speech recognition.
|
||||
"""
|
||||
@classmethod
|
||||
def create_phrases(cls,
|
||||
model: str,
|
||||
phrases: Dict[str, Any],
|
||||
training_type: str = 'compile_asr_phrase',
|
||||
**kwargs) -> DashScopeAPIResponse:
|
||||
"""Create hot words.
|
||||
|
||||
Args:
|
||||
model (str): The requested model.
|
||||
phrases (Dict[str, Any]): A dictionary that contains phrases,
|
||||
such as {'下一首':90,'上一首':90}.
|
||||
training_type (str, `optional`): The training type,
|
||||
'compile_asr_phrase' is default.
|
||||
|
||||
Raises:
|
||||
InvalidParameter: Parameter input is None or empty!
|
||||
|
||||
Returns:
|
||||
DashScopeAPIResponse: The results of creating hot words.
|
||||
"""
|
||||
if phrases is None or len(phrases) == 0:
|
||||
raise InvalidParameter('phrases is empty!')
|
||||
if training_type is None or len(training_type) == 0:
|
||||
raise InvalidParameter('training_type is empty!')
|
||||
|
||||
original_ft_sub_path = FineTune.SUB_PATH
|
||||
FineTune.SUB_PATH = 'fine-tunes'
|
||||
response = FineTune.call(model=model,
|
||||
training_file_ids=[],
|
||||
validation_file_ids=[],
|
||||
mode=training_type,
|
||||
hyper_parameters={'phrase_list': phrases},
|
||||
**kwargs)
|
||||
FineTune.SUB_PATH = original_ft_sub_path
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
logger.error('Create phrase failed, ' + str(response))
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def update_phrases(cls,
|
||||
model: str,
|
||||
phrase_id: str,
|
||||
phrases: Dict[str, Any],
|
||||
training_type: str = 'compile_asr_phrase',
|
||||
**kwargs) -> DashScopeAPIResponse:
|
||||
"""Update the hot words marked phrase_id.
|
||||
|
||||
Args:
|
||||
model (str): The requested model.
|
||||
phrase_id (str): The ID of phrases,
|
||||
which created by create_phrases().
|
||||
phrases (Dict[str, Any]): A dictionary that contains phrases,
|
||||
such as {'暂停':90}.
|
||||
training_type (str, `optional`):
|
||||
The training type, 'compile_asr_phrase' is default.
|
||||
|
||||
Raises:
|
||||
InvalidParameter: Parameter input is None or empty!
|
||||
|
||||
Returns:
|
||||
DashScopeAPIResponse: The results of updating hot words.
|
||||
"""
|
||||
if phrase_id is None or len(phrase_id) == 0:
|
||||
raise InvalidParameter('phrase_id is empty!')
|
||||
if phrases is None or len(phrases) == 0:
|
||||
raise InvalidParameter('phrases is empty!')
|
||||
if training_type is None or len(training_type) == 0:
|
||||
raise InvalidParameter('training_type is empty!')
|
||||
|
||||
original_ft_sub_path = FineTune.SUB_PATH
|
||||
FineTune.SUB_PATH = 'fine-tunes'
|
||||
response = FineTune.call(model=model,
|
||||
training_file_ids=[],
|
||||
validation_file_ids=[],
|
||||
mode=training_type,
|
||||
hyper_parameters={'phrase_list': phrases},
|
||||
finetuned_output=phrase_id,
|
||||
**kwargs)
|
||||
FineTune.SUB_PATH = original_ft_sub_path
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
logger.error('Update phrase failed, ' + str(response))
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def query_phrases(cls, phrase_id: str, **kwargs) -> DashScopeAPIResponse:
|
||||
"""Query the hot words by phrase_id.
|
||||
|
||||
Args:
|
||||
phrase_id (str): The ID of phrases,
|
||||
which created by create_phrases().
|
||||
|
||||
Raises:
|
||||
InvalidParameter: phrase_id input is None or empty!
|
||||
|
||||
Returns:
|
||||
AsrPhraseManagerResult: The results of querying hot words.
|
||||
"""
|
||||
if phrase_id is None or len(phrase_id) == 0:
|
||||
raise InvalidParameter('phrase_id is empty!')
|
||||
|
||||
original_ft_sub_path = FineTune.SUB_PATH
|
||||
FineTune.SUB_PATH = 'fine-tunes/outputs'
|
||||
response = FineTune.get(job_id=phrase_id, **kwargs)
|
||||
FineTune.SUB_PATH = original_ft_sub_path
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
logger.error('Query phrase failed, ' + str(response))
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def list_phrases(cls,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
**kwargs) -> DashScopeAPIResponse:
|
||||
"""List all information of phrases.
|
||||
|
||||
Args:
|
||||
page (int): Page number, greater than 0, default value 1.
|
||||
page_size (int): The paging size, greater than 0
|
||||
and less than or equal to 100, default value 10.
|
||||
|
||||
Returns:
|
||||
DashScopeAPIResponse: The results of listing hot words.
|
||||
"""
|
||||
original_ft_sub_path = FineTune.SUB_PATH
|
||||
FineTune.SUB_PATH = 'fine-tunes/outputs'
|
||||
response = FineTune.list(page=page, page_size=page_size, **kwargs)
|
||||
FineTune.SUB_PATH = original_ft_sub_path
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
logger.error('List phrase failed, ' + str(response))
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def delete_phrases(cls, phrase_id: str, **kwargs) -> DashScopeAPIResponse:
|
||||
"""Delete the hot words by phrase_id.
|
||||
|
||||
Args:
|
||||
phrase_id (str): The ID of phrases,
|
||||
which created by create_phrases().
|
||||
|
||||
Raises:
|
||||
InvalidParameter: phrase_id input is None or empty!
|
||||
|
||||
Returns:
|
||||
DashScopeAPIResponse: The results of deleting hot words.
|
||||
"""
|
||||
if phrase_id is None or len(phrase_id) == 0:
|
||||
raise InvalidParameter('phrase_id is empty!')
|
||||
|
||||
original_ft_sub_path = FineTune.SUB_PATH
|
||||
FineTune.SUB_PATH = 'fine-tunes/outputs'
|
||||
response = FineTune.delete(job_id=phrase_id, **kwargs)
|
||||
FineTune.SUB_PATH = original_ft_sub_path
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
logger.error('Delete phrase failed, ' + str(response))
|
||||
|
||||
return response
|
||||
@@ -0,0 +1,447 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
from http import HTTPStatus
|
||||
from threading import Timer
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from dashscope.api_entities.dashscope_response import RecognitionResponse
|
||||
from dashscope.client.base_api import BaseApi
|
||||
from dashscope.common.constants import ApiProtocol
|
||||
from dashscope.common.error import (InputDataRequired, InputRequired,
|
||||
InvalidParameter, InvalidTask,
|
||||
ModelRequired)
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.common.utils import _get_task_group_and_task
|
||||
from dashscope.protocol.websocket import WebsocketStreamingMode
|
||||
|
||||
|
||||
class RecognitionResult(RecognitionResponse):
|
||||
"""The result set of speech recognition, including the single-sentence
|
||||
recognition result returned by the callback mode, and all recognition
|
||||
results in a synchronized manner.
|
||||
"""
|
||||
def __init__(self,
|
||||
response: RecognitionResponse,
|
||||
sentences: List[Any] = None,
|
||||
usages: List[Any] = None):
|
||||
self.status_code = response.status_code
|
||||
self.request_id = response.request_id
|
||||
self.code = response.code
|
||||
self.message = response.message
|
||||
self.usages = usages
|
||||
if sentences is not None and len(sentences) > 0:
|
||||
self.output = {'sentence': sentences}
|
||||
else:
|
||||
self.output = response.output
|
||||
if self.usages is not None and len(
|
||||
self.usages) > 0 and 'usage' in self.usages[-1]:
|
||||
self.usage = self.usages[-1]['usage']
|
||||
else:
|
||||
self.usage = None
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps(RecognitionResponse.from_api_response(self),
|
||||
ensure_ascii=False)
|
||||
|
||||
def get_sentence(self) -> Union[Dict[str, Any], List[Any]]:
|
||||
"""The result of speech recognition.
|
||||
"""
|
||||
if self.output and 'sentence' in self.output:
|
||||
return self.output['sentence']
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_request_id(self) -> str:
|
||||
"""The request_id of speech recognition.
|
||||
"""
|
||||
return self.request_id
|
||||
|
||||
def get_usage(self, sentence: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get billing for the input sentence.
|
||||
"""
|
||||
if self.usages is not None:
|
||||
if sentence is not None and 'end_time' in sentence and sentence[
|
||||
'end_time'] is not None:
|
||||
for usage in self.usages:
|
||||
if usage['end_time'] == sentence['end_time']:
|
||||
return usage['usage']
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def is_sentence_end(sentence: Dict[str, Any]) -> bool:
|
||||
"""Determine whether the speech recognition result is the end of a sentence.
|
||||
This is a static method.
|
||||
"""
|
||||
if sentence is not None and 'end_time' in sentence and sentence[
|
||||
'end_time'] is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class RecognitionCallback():
|
||||
"""An interface that defines callback methods for getting speech recognition results. # noqa E501
|
||||
Derive from this class and implement its function to provide your own data.
|
||||
"""
|
||||
def on_open(self) -> None:
|
||||
pass
|
||||
|
||||
def on_complete(self) -> None:
|
||||
pass
|
||||
|
||||
def on_error(self, result: RecognitionResult) -> None:
|
||||
pass
|
||||
|
||||
def on_close(self) -> None:
|
||||
pass
|
||||
|
||||
def on_event(self, result: RecognitionResult) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Recognition(BaseApi):
|
||||
"""Speech recognition interface.
|
||||
|
||||
Args:
|
||||
model (str): The requested model_id.
|
||||
callback (RecognitionCallback): A callback that returns
|
||||
speech recognition results.
|
||||
format (str): The input audio format for speech recognition.
|
||||
sample_rate (int): The input audio sample rate for speech recognition.
|
||||
|
||||
**kwargs:
|
||||
phrase_id (list, `optional`): The ID of phrase.
|
||||
disfluency_removal_enabled(bool, `optional`): Filter mood words,
|
||||
turned off by default.
|
||||
diarization_enabled (bool, `optional`): Speech auto diarization,
|
||||
turned off by default.
|
||||
speaker_count (int, `optional`): The number of speakers.
|
||||
timestamp_alignment_enabled (bool, `optional`): Timestamp-alignment
|
||||
calibration, turned off by default.
|
||||
special_word_filter(str, `optional`): Sensitive word filter.
|
||||
audio_event_detection_enabled(bool, `optional`):
|
||||
Audio event detection, turned off by default.
|
||||
|
||||
Raises:
|
||||
InputRequired: Input is required.
|
||||
"""
|
||||
|
||||
SILENCE_TIMEOUT_S = 23
|
||||
|
||||
def __init__(self, model: str, callback: RecognitionCallback, format: str,
|
||||
sample_rate: int, **kwargs):
|
||||
if model is None:
|
||||
raise ModelRequired('Model is required!')
|
||||
if format is None:
|
||||
raise InputRequired('format is required!')
|
||||
if sample_rate is None:
|
||||
raise InputRequired('sample_rate is required!')
|
||||
|
||||
self.model = model
|
||||
self.format = format
|
||||
self.sample_rate = sample_rate
|
||||
# continuous recognition with start() or once recognition with call()
|
||||
self._recognition_once = False
|
||||
self._callback = callback
|
||||
self._running = False
|
||||
self._stream_data = []
|
||||
self._worker = None
|
||||
self._silence_timer = None
|
||||
self._kwargs = kwargs
|
||||
|
||||
def __del__(self):
|
||||
if self._running:
|
||||
self._running = False
|
||||
self._stream_data.clear()
|
||||
if self._worker is not None and self._worker.is_alive():
|
||||
self._worker.join()
|
||||
if self._silence_timer is not None and self._silence_timer.is_alive( # noqa E501
|
||||
):
|
||||
self._silence_timer.cancel()
|
||||
self._silence_timer = None
|
||||
if self._callback:
|
||||
self._callback.on_close()
|
||||
|
||||
def __receive_worker(self):
|
||||
"""Asynchronously, initiate a real-time speech recognition request and
|
||||
obtain the result for parsing.
|
||||
"""
|
||||
responses = self.__launch_request()
|
||||
for part in responses:
|
||||
if part.status_code == HTTPStatus.OK:
|
||||
if len(part.output) == 0:
|
||||
self._callback.on_complete()
|
||||
else:
|
||||
usage: Dict[str, Any] = None
|
||||
useags: List[Any] = None
|
||||
if 'sentence' in part.output and part.usage is not None:
|
||||
usage = {
|
||||
'end_time': part.output['sentence']['end_time'],
|
||||
'usage': part.usage
|
||||
}
|
||||
useags = [usage]
|
||||
|
||||
self._callback.on_event(
|
||||
RecognitionResult(
|
||||
RecognitionResponse.from_api_response(part),
|
||||
usages=useags))
|
||||
else:
|
||||
self._running = False
|
||||
self._stream_data.clear()
|
||||
self._callback.on_error(
|
||||
RecognitionResult(
|
||||
RecognitionResponse.from_api_response(part)))
|
||||
self._callback.on_close()
|
||||
break
|
||||
|
||||
def __launch_request(self):
|
||||
"""Initiate real-time speech recognition requests.
|
||||
"""
|
||||
resources_list: list = []
|
||||
if self._phrase is not None and len(self._phrase) > 0:
|
||||
item = {'resource_id': self._phrase, 'resource_type': 'asr_phrase'}
|
||||
resources_list.append(item)
|
||||
|
||||
if len(resources_list) > 0:
|
||||
self._kwargs['resources'] = resources_list
|
||||
|
||||
self._tidy_kwargs()
|
||||
task_name, _ = _get_task_group_and_task(__name__)
|
||||
responses = super().call(model=self.model,
|
||||
task_group='audio',
|
||||
task=task_name,
|
||||
function='recognition',
|
||||
input=self._input_stream_cycle(),
|
||||
api_protocol=ApiProtocol.WEBSOCKET,
|
||||
ws_stream_mode=WebsocketStreamingMode.DUPLEX,
|
||||
is_binary_input=True,
|
||||
sample_rate=self.sample_rate,
|
||||
format=self.format,
|
||||
stream=True,
|
||||
**self._kwargs)
|
||||
return responses
|
||||
|
||||
def start(self, phrase_id: str = None, **kwargs):
|
||||
"""Real-time speech recognition in asynchronous mode.
|
||||
Please call 'stop()' after you have completed recognition.
|
||||
|
||||
Args:
|
||||
phrase_id (str, `optional`): The ID of phrase.
|
||||
|
||||
**kwargs:
|
||||
disfluency_removal_enabled(bool, `optional`):
|
||||
Filter mood words, turned off by default.
|
||||
diarization_enabled (bool, `optional`):
|
||||
Speech auto diarization, turned off by default.
|
||||
speaker_count (int, `optional`): The number of speakers.
|
||||
timestamp_alignment_enabled (bool, `optional`):
|
||||
Timestamp-alignment calibration, turned off by default.
|
||||
special_word_filter(str, `optional`): Sensitive word filter.
|
||||
audio_event_detection_enabled(bool, `optional`):
|
||||
Audio event detection, turned off by default.
|
||||
|
||||
Raises:
|
||||
InvalidParameter: This interface cannot be called again
|
||||
if it has already been started.
|
||||
InvalidTask: Task create failed.
|
||||
"""
|
||||
assert self._callback is not None, 'Please set the callback to get the speech recognition result.' # noqa E501
|
||||
|
||||
if self._running:
|
||||
raise InvalidParameter('Speech recognition has started.')
|
||||
|
||||
self._phrase = phrase_id
|
||||
self._kwargs.update(**kwargs)
|
||||
self._recognition_once = False
|
||||
self._worker = threading.Thread(target=self.__receive_worker)
|
||||
self._worker.start()
|
||||
if self._worker.is_alive():
|
||||
self._running = True
|
||||
self._callback.on_open()
|
||||
|
||||
# If audio data is not received for 23 seconds, the timeout exits
|
||||
self._silence_timer = Timer(Recognition.SILENCE_TIMEOUT_S,
|
||||
self._silence_stop_timer)
|
||||
self._silence_timer.start()
|
||||
else:
|
||||
self._running = False
|
||||
raise InvalidTask('Invalid task, task create failed.')
|
||||
|
||||
def call(self,
|
||||
file: str,
|
||||
phrase_id: str = None,
|
||||
**kwargs) -> RecognitionResult:
|
||||
"""Real-time speech recognition in synchronous mode.
|
||||
|
||||
Args:
|
||||
file (str): The path to the local audio file.
|
||||
phrase_id (str, `optional`): The ID of phrase.
|
||||
|
||||
**kwargs:
|
||||
disfluency_removal_enabled(bool, `optional`):
|
||||
Filter mood words, turned off by default.
|
||||
diarization_enabled (bool, `optional`):
|
||||
Speech auto diarization, turned off by default.
|
||||
speaker_count (int, `optional`): The number of speakers.
|
||||
timestamp_alignment_enabled (bool, `optional`):
|
||||
Timestamp-alignment calibration, turned off by default.
|
||||
special_word_filter(str, `optional`): Sensitive word filter.
|
||||
audio_event_detection_enabled(bool, `optional`):
|
||||
Audio event detection, turned off by default.
|
||||
|
||||
Raises:
|
||||
InvalidParameter: This interface cannot be called again
|
||||
if it has already been started.
|
||||
InputDataRequired: The supplied file was empty.
|
||||
|
||||
Returns:
|
||||
RecognitionResult: The result of speech recognition.
|
||||
"""
|
||||
if self._running:
|
||||
raise InvalidParameter('Speech recognition has been called.')
|
||||
|
||||
if os.path.exists(file):
|
||||
if os.path.isdir(file):
|
||||
raise IsADirectoryError('Is a directory: ' + file)
|
||||
else:
|
||||
raise FileNotFoundError('No such file or directory: ' + file)
|
||||
|
||||
self._recognition_once = True
|
||||
self._stream_data.clear()
|
||||
self._phrase = phrase_id
|
||||
self._kwargs.update(**kwargs)
|
||||
error_flag: bool = False
|
||||
sentences: List[Any] = []
|
||||
usages: List[Any] = []
|
||||
response: RecognitionResponse = None
|
||||
result: RecognitionResult = None
|
||||
|
||||
try:
|
||||
audio_data: bytes = None
|
||||
f = open(file, 'rb')
|
||||
if os.path.getsize(file):
|
||||
while True:
|
||||
audio_data = f.read(12800)
|
||||
if not audio_data:
|
||||
break
|
||||
else:
|
||||
self._stream_data = self._stream_data + [audio_data]
|
||||
else:
|
||||
raise InputDataRequired(
|
||||
'The supplied file was empty (zero bytes long)')
|
||||
f.close()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise e
|
||||
|
||||
if self._stream_data is not None and len(self._stream_data) > 0:
|
||||
self._running = True
|
||||
responses = self.__launch_request()
|
||||
for part in responses:
|
||||
if part.status_code == HTTPStatus.OK:
|
||||
if 'sentence' in part.output:
|
||||
sentence = part.output['sentence']
|
||||
if RecognitionResult.is_sentence_end(sentence):
|
||||
sentences.append(sentence)
|
||||
|
||||
if part.usage is not None:
|
||||
usage = {
|
||||
'end_time':
|
||||
part.output['sentence']['end_time'],
|
||||
'usage': part.usage
|
||||
}
|
||||
usages.append(usage)
|
||||
|
||||
response = RecognitionResponse.from_api_response(part)
|
||||
else:
|
||||
response = RecognitionResponse.from_api_response(part)
|
||||
logger.error(response)
|
||||
error_flag = True
|
||||
break
|
||||
|
||||
if error_flag:
|
||||
result = RecognitionResult(response)
|
||||
else:
|
||||
result = RecognitionResult(response, sentences, usages)
|
||||
|
||||
self._stream_data.clear()
|
||||
self._recognition_once = False
|
||||
self._running = False
|
||||
|
||||
return result
|
||||
|
||||
def stop(self):
|
||||
"""End asynchronous speech recognition.
|
||||
|
||||
Raises:
|
||||
InvalidParameter: Cannot stop an uninitiated recognition.
|
||||
"""
|
||||
if self._running is False:
|
||||
raise InvalidParameter('Speech recognition has stopped.')
|
||||
|
||||
self._running = False
|
||||
if self._worker is not None and self._worker.is_alive():
|
||||
self._worker.join()
|
||||
self._stream_data.clear()
|
||||
if self._silence_timer is not None and self._silence_timer.is_alive():
|
||||
self._silence_timer.cancel()
|
||||
self._silence_timer = None
|
||||
if self._callback:
|
||||
self._callback.on_close()
|
||||
|
||||
def send_audio_frame(self, buffer: bytes):
|
||||
"""Push speech recognition.
|
||||
|
||||
Raises:
|
||||
InvalidParameter: Cannot send data to an uninitiated recognition.
|
||||
"""
|
||||
if self._running is False:
|
||||
raise InvalidParameter('Speech recognition has stopped.')
|
||||
|
||||
self._stream_data = self._stream_data + [buffer]
|
||||
|
||||
def _tidy_kwargs(self):
|
||||
for k in self._kwargs.copy():
|
||||
if self._kwargs[k] is None:
|
||||
self._kwargs.pop(k, None)
|
||||
|
||||
def _input_stream_cycle(self):
|
||||
while self._running:
|
||||
while len(self._stream_data) == 0:
|
||||
if self._running:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
# Reset silence_timer when getting stream.
|
||||
if self._silence_timer is not None and self._silence_timer.is_alive( # noqa E501
|
||||
):
|
||||
self._silence_timer.cancel()
|
||||
self._silence_timer = Timer(Recognition.SILENCE_TIMEOUT_S,
|
||||
self._silence_stop_timer)
|
||||
self._silence_timer.start()
|
||||
|
||||
for frame in self._stream_data:
|
||||
yield bytes(frame)
|
||||
self._stream_data.clear()
|
||||
|
||||
if self._recognition_once:
|
||||
self._running = False
|
||||
|
||||
# drain all audio data when invoking stop().
|
||||
if self._recognition_once is False:
|
||||
for frame in self._stream_data:
|
||||
yield bytes(frame)
|
||||
|
||||
def _silence_stop_timer(self):
|
||||
"""If audio data is not received for a long time, exit worker.
|
||||
"""
|
||||
self._running = False
|
||||
if self._silence_timer is not None and self._silence_timer.is_alive():
|
||||
self._silence_timer.cancel()
|
||||
self._silence_timer = None
|
||||
if self._worker is not None and self._worker.is_alive():
|
||||
self._worker.join()
|
||||
self._stream_data.clear()
|
||||
@@ -0,0 +1,209 @@
|
||||
import asyncio
|
||||
import time
|
||||
from typing import List, Union
|
||||
|
||||
import aiohttp
|
||||
|
||||
from dashscope.api_entities.dashscope_response import (DashScopeAPIResponse,
|
||||
TranscriptionResponse)
|
||||
from dashscope.client.base_api import BaseAsyncApi
|
||||
from dashscope.common.constants import ApiProtocol, HTTPMethod
|
||||
from dashscope.common.logging import logger
|
||||
from dashscope.common.utils import _get_task_group_and_task
|
||||
|
||||
|
||||
class Transcription(BaseAsyncApi):
|
||||
"""API for File Transcription models.
|
||||
"""
|
||||
|
||||
MAX_QUERY_TRY_COUNT = 3
|
||||
|
||||
class Models:
|
||||
paraformer_v1 = 'paraformer-v1'
|
||||
paraformer_8k_v1 = 'paraformer-8k-v1'
|
||||
paraformer_mtl_v1 = 'paraformer-mtl-v1'
|
||||
|
||||
@classmethod
|
||||
def call(cls,
|
||||
model: str,
|
||||
file_urls: List[str],
|
||||
phrase_id: str = None,
|
||||
api_key: str = None,
|
||||
**kwargs) -> TranscriptionResponse:
|
||||
"""Transcribe the given files synchronously.
|
||||
|
||||
Args:
|
||||
model (str): The requested model_id.
|
||||
file_urls (List[str]): List of stored URLs.
|
||||
phrase_id (str, `optional`): The ID of phrase.
|
||||
|
||||
**kwargs:
|
||||
channel_id (List[int], optional):
|
||||
The selected channel_id of audio file.
|
||||
disfluency_removal_enabled(bool, `optional`):
|
||||
Filter mood words, turned off by default.
|
||||
diarization_enabled (bool, `optional`):
|
||||
Speech auto diarization, turned off by default.
|
||||
speaker_count (int, `optional`): The number of speakers.
|
||||
timestamp_alignment_enabled (bool, `optional`):
|
||||
Timestamp-alignment calibration, turned off by default.
|
||||
special_word_filter(str, `optional`): Sensitive word filter.
|
||||
audio_event_detection_enabled(bool, `optional`):
|
||||
Audio event detection, turned off by default.
|
||||
|
||||
Returns:
|
||||
TranscriptionResponse: The result of batch transcription.
|
||||
"""
|
||||
kwargs.update(cls._fill_resource_id(phrase_id, **kwargs))
|
||||
kwargs = cls._tidy_kwargs(**kwargs)
|
||||
response = super().call(model, file_urls, api_key=api_key, **kwargs)
|
||||
return TranscriptionResponse.from_api_response(response)
|
||||
|
||||
@classmethod
|
||||
def async_call(cls,
|
||||
model: str,
|
||||
file_urls: List[str],
|
||||
phrase_id: str = None,
|
||||
api_key: str = None,
|
||||
**kwargs) -> TranscriptionResponse:
|
||||
"""Transcribe the given files asynchronously,
|
||||
return the status of task submission for querying results subsequently.
|
||||
|
||||
Args:
|
||||
model (str): The requested model, such as paraformer-16k-1
|
||||
file_urls (List[str]): List of stored URLs.
|
||||
phrase_id (str, `optional`): The ID of phrase.
|
||||
|
||||
**kwargs:
|
||||
channel_id (List[int], optional):
|
||||
The selected channel_id of audio file.
|
||||
disfluency_removal_enabled(bool, `optional`):
|
||||
Filter mood words, turned off by default.
|
||||
diarization_enabled (bool, `optional`):
|
||||
Speech auto diarization, turned off by default.
|
||||
speaker_count (int, `optional`): The number of speakers.
|
||||
timestamp_alignment_enabled (bool, `optional`):
|
||||
Timestamp-alignment calibration, turned off by default.
|
||||
special_word_filter(str, `optional`): Sensitive word filter.
|
||||
audio_event_detection_enabled(bool, `optional`):
|
||||
Audio event detection, turned off by default.
|
||||
|
||||
Returns:
|
||||
TranscriptionResponse: The response including task_id.
|
||||
"""
|
||||
kwargs.update(cls._fill_resource_id(phrase_id, **kwargs))
|
||||
kwargs = cls._tidy_kwargs(**kwargs)
|
||||
response = cls._launch_request(model,
|
||||
file_urls,
|
||||
api_key=api_key,
|
||||
**kwargs)
|
||||
return TranscriptionResponse.from_api_response(response)
|
||||
|
||||
@classmethod
|
||||
def fetch(
|
||||
cls,
|
||||
task: Union[str, TranscriptionResponse],
|
||||
api_key: str = None,
|
||||
) -> TranscriptionResponse:
|
||||
"""Fetch the status of task, including results of batch transcription when task_status is SUCCEEDED. # noqa: E501
|
||||
|
||||
Args:
|
||||
task (Union[str, TranscriptionResponse]): The task_id or
|
||||
response including task_id returned from async_call().
|
||||
|
||||
Returns:
|
||||
TranscriptionResponse: The status of task_id,
|
||||
including results of batch transcription when task_status is SUCCEEDED.
|
||||
"""
|
||||
try_count: int = 0
|
||||
while True:
|
||||
try:
|
||||
response = super().fetch(task, api_key=api_key)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientConnectorError) as e:
|
||||
logger.error(e)
|
||||
try_count += 1
|
||||
if try_count <= Transcription.MAX_QUERY_TRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
try_count = 0
|
||||
break
|
||||
|
||||
return TranscriptionResponse.from_api_response(response)
|
||||
|
||||
@classmethod
|
||||
def wait(
|
||||
cls,
|
||||
task: Union[str, TranscriptionResponse],
|
||||
api_key: str = None,
|
||||
) -> TranscriptionResponse:
|
||||
"""Poll task until the final results of transcription is obtained.
|
||||
|
||||
Args:
|
||||
task (Union[str, TranscriptionResponse]): The task_id or
|
||||
response including task_id returned from async_call().
|
||||
|
||||
Returns:
|
||||
TranscriptionResponse: The result of batch transcription.
|
||||
"""
|
||||
response = super().wait(task, api_key=api_key)
|
||||
return TranscriptionResponse.from_api_response(response)
|
||||
|
||||
@classmethod
|
||||
def _launch_request(cls,
|
||||
model: str,
|
||||
files: List[str],
|
||||
api_key: str = None,
|
||||
**kwargs) -> DashScopeAPIResponse:
|
||||
"""Submit transcribe request.
|
||||
|
||||
Args:
|
||||
model (str): The requested model, such as paraformer-16k-1
|
||||
files (List[str]): List of stored URLs.
|
||||
|
||||
Returns:
|
||||
DashScopeAPIResponse: The result of task submission.
|
||||
"""
|
||||
task_name, function = _get_task_group_and_task(__name__)
|
||||
|
||||
try_count: int = 0
|
||||
while True:
|
||||
try:
|
||||
response = super().async_call(model=model,
|
||||
task_group='audio',
|
||||
task=task_name,
|
||||
function=function,
|
||||
input={'file_urls': files},
|
||||
api_protocol=ApiProtocol.HTTP,
|
||||
http_method=HTTPMethod.POST,
|
||||
api_key=api_key,
|
||||
**kwargs)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientConnectorError) as e:
|
||||
logger.error(e)
|
||||
try_count += 1
|
||||
if try_count <= Transcription.MAX_QUERY_TRY_COUNT:
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def _fill_resource_id(cls, phrase_id: str, **kwargs):
|
||||
resources_list: list = []
|
||||
if phrase_id is not None and len(phrase_id) > 0:
|
||||
item = {'resource_id': phrase_id, 'resource_type': 'asr_phrase'}
|
||||
resources_list.append(item)
|
||||
|
||||
if len(resources_list) > 0:
|
||||
kwargs['resources'] = resources_list
|
||||
|
||||
return kwargs
|
||||
|
||||
@classmethod
|
||||
def _tidy_kwargs(cls, **kwargs):
|
||||
for k in kwargs.copy():
|
||||
if kwargs[k] is None:
|
||||
kwargs.pop(k, None)
|
||||
return kwargs
|
||||
Reference in New Issue
Block a user