This commit is contained in:
taofajun 2020-06-29 17:12:26 +08:00
parent e36964859a
commit d251763f73
8 changed files with 337 additions and 30 deletions

View File

@ -0,0 +1,7 @@
<?php
namespace SpeechSynthesis\Contracts;
interface AudioSynthesisStrategy
{
public function textToAudio(string $fileName):array ;
}

21
src/Example/Index.php Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace SpeechSynthesis\Example;
use SpeechSynthesis\SynthesisFactory;
class Index
{
public function test(){
$config = [
'Iflytek'=>[
'fileRoot'=>'/'
]
];
$syn = new SynthesisFactory('Iflytek','测试的名字',$config);
$msg = $syn->getErrorMsg();
$speechFile = $syn->getSpeechFile();
}
}

12
src/Example/indexs.php Normal file
View File

@ -0,0 +1,12 @@
<?php
use SpeechSynthesis\Example\Index;
require 'Index.php';
require '../Contracts/AudioSynthesisStrategy.php';
require '../SynthesisFactory.php';
require '../Products/Config.php';
require '../Products/Iflytek.php';
$res = new Index();
$res->test();

View File

@ -1,17 +0,0 @@
<?php
namespace SpeechSynthesis;
use SpeechSynthesis\Test\Tests;
class Index
{
public function test()
{
$test = new Tests();
echo 'success';
echo $test->index();
}
}

77
src/Products/Config.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace SpeechSynthesis\Products;
class Config
{
static private $instance;
public function __construct()
{
}
public static function getInstance()
{
//判断$instance是否是Singleton的对象不是则创建
if (!self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}
/**
* @var array
*/
protected static $config = [
'Iflytek'=>[
'fileRoot'=>'/',
'url'=>'wss://tts-api.xfyun.cn/v2/tts',
'algorithm'=>'hmac-sha256',
'time'=>'',
'app_id' => '5e840bb8',
'api_secret'=>'0b0f729b9e5302ba69f5b91aba91948f',
'api_key'=>'09536ff9b5aeb8a4ccb51121b7844092',
'aue' => 'lame',
'sfl' => 1,
'auf' => 'audio/L16;rate=16000',
'vcn' => 'xiaoyan',
'speed' => 50,
'volume' => 100,
'pitch' => 50,
'tte' => 'UTF8',
'ent' => 'intp65',
'rdn' => '0',
'ram' => '0',
'reg' => '2',
'bgs' => 0,
'status' => 2,
'draftContent' => '测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。
测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。
测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。
测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。
测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。
测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。测试一下哈哈哈,测试一下哈哈哈。',
]
];
/**
* @param $type
* @return array
*/
public function getConfig(string $type): array
{
return self::$config[$type];
}
/**
* @param array $config
*/
public function setConfig(array $config)
{
self::$config = array_merge($config, self::$config);
}
}

164
src/Products/Iflytek.php Normal file
View File

@ -0,0 +1,164 @@
<?php
namespace SpeechSynthesis\Products;
use SpeechSynthesis\Contracts\AudioSynthesisStrategy;
use WebSocket\Client;
class Iflytek implements AudioSynthesisStrategy
{
/**
* @var array
* 科大讯飞配置
*/
private $config;
/**
* Iflytek constructor.
* @param array $config
* @throws \Exception
*/
public function __construct()
{
$key = __CLASS__;
$len = strripos($key,'\\');
if( $len !== false){
$key = substr('peechSynthesis\Products\Iflytek', $len);
}
$configClass = Config::getInstance();
$this->config = $configClass->getConfig($key);
$this->config['time'] = date('D, d M Y H:i:s', strtotime('-8 hours')) . ' GMT';
}
/**
* 生成音频文件
* @param $fileName
* @return array
* code , msg, data
*/
public function textToAudio(string $fileName):array
{
if(empty($this->config['fileRoot'])){
return ['code'=>0, 'msg' => '文件地址不能为空', 'data'=>['audio_name'=>'']];
}
if(!is_dir($this->config['fileRoot'])){
@mkdir($this->config['fileRoot'], 0755, true);
}
// 设置文件名
$ext = $this->config['sfl'] ? '.mp3' : '.pcm';
$fileName = $fileName . $ext;
// 完整文件地址
$fullFileName = $this->config['fileRoot'] . DIRECTORY_SEPARATOR . $fileName;
// 文件冲突重新命名一下后缀加_new
if(file_exists($fullFileName)){
$fullFileName = $this->config['fileRoot'] . DIRECTORY_SEPARATOR . md5($fileName).'_new'.$ext;
}
/**
* 开始合成
*/
try {
// 链接的url
$url = $this->createUrl();
// 要发送的消息
$message = $this->createMsg();
// 建立链接
$client = new Client($url,['timeout'=>60]);
// 发送请求
$client->send(json_encode($message, true));
//需要以追加的方式进行写文件
$audioFile = fopen($fullFileName, 'ab');
// 科大讯飞会分多次发送消息
do {
//持续接收消息
$response = $client->receive();
$response = json_decode($response, true);
// 不成功会返回错误 code为0是成功
if ($response['code']) {
return ['code' => 0, 'msg' => $response, 'data'=>['audio_name'=>'']];
}
//返回的音频需要进行base64解码
$audio = base64_decode($response['data']['audio']);
// 追加
fwrite($audioFile, $audio);
} while ($response['data']['status'] !== 2);// status=2代表发送完成
fclose($audioFile);
return ['code' => 1, 'msg' => '合成成功', 'data' => ['audio_name' => $fileName]];
} catch (\Exception $e) {
return ['code' => 0, 'msg' => $e->getMessage(), 'data'=>['audio_name'=>'']];
} finally {
// 不管怎样,都需关掉连接
$client->close();
}
}
/**
* 创建参数
*/
private function createMsg():array
{
return [
'common' => [
'app_id' => $this->config['app_id'],
],
'business' => [
'aue' => $this->config['aue'],
'sfl' => $this->config['sfl'],
'auf' => $this->config['auf'],
'vcn' => $this->config['vcn'],
'speed' => $this->config['speed'],
'volume' => $this->config['volume'],
'pitch' => $this->config['pitch'],
'tte' => $this->config['tte'],
'ent' => $this->config['ent'],
'rdn' => $this->config['rdn'],
'ram' => $this->config['ram'],
'reg' => $this->config['reg'],
'bgs' => $this->config['bgs']
],
'data' => [
'status' => 2,
'text' => base64_encode($this->config['draftContent']),
],
];
}
/**
* 创建url
* @return string
*/
private function createUrl():string
{
// 生成签名
$urlInfo = parse_url($this->config['url']);
$signatureOrigin = 'host: ' . $urlInfo['host'] . "\n";
$signatureOrigin .= 'date: ' . $this->config['time'] . "\n";
$signatureOrigin .= 'GET ' . $urlInfo['path'] . ' HTTP/1.1';
$signatureSha = hash_hmac('sha256', $signatureOrigin, $this->config['api_secret'], true);
$signatureSha = base64_encode($signatureSha);
$authorizationOrigin = 'api_key="' . $this->config['api_key'] . '", algorithm="' . $this->config['algorithm'] . '", ';
$authorizationOrigin .= 'headers="host date request-line", signature="' . $signatureSha . '"';
$authorization = base64_encode($authorizationOrigin);
// 生成Url
$signUrl = $this->config['url'] . '?' . 'authorization=' . $authorization . '&date='
. urlencode($this->config['time']) . '&host=' . $urlInfo['host'];
return $signUrl;
}
}

56
src/SynthesisFactory.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace SpeechSynthesis;
use SpeechSynthesis\Contracts\AudioSynthesisStrategy;
use SpeechSynthesis\Products\Config;
use SpeechSynthesis\Products\Iflytek;
use traits\think\Instance;
class SynthesisFactory
{
private $synthesisRes = [];
public function __construct($synthesisType,$filename, $config = [])
{
// 增加配置
if($config){
$configClass = Config::getInstance();
$configClass->setConfig($config);
}
$synthesisClass = new Iflytek();
$synthesisRes = [];
if($synthesisClass instanceof AudioSynthesisStrategy){
$synthesisRes = (new Iflytek())->textToAudio($filename);
}
$this->synthesisRes = $synthesisRes;
}
/**
* 文件名
* @return mixed
*/
public function getSpeechFile():string
{
return $this->synthesisRes['audio_name'];
}
/**
* 返回错误信息
* false代表没有错误
* @return bool|mixed|string
*/
public function getErrorMsg()
{
if(!$this->synthesisRes){
return '出错了,没有任何数据';
}
if($this->synthesisRes['code'] === 0){
return $this->synthesisRes['msg'];
}
return false;
}
}

View File

@ -1,13 +0,0 @@
<?php
namespace SpeechSynthesis\Test;
class Tests
{
public function index()
{
return 'success';
}
}