xiaoai/php_server/app/api/logic/HumanLogic.php

2551 lines
93 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\api\logic;
use app\api\logic\service\TokenLogService;
use app\common\enum\user\AccountLogEnum;
use app\common\logic\AccountLogLogic;
use app\common\model\human\HumanAnchor;
use app\common\model\human\HumanAudio;
use app\common\model\human\HumanTask;
use app\common\model\human\HumanVideo;
use app\common\model\human\HumanVideoTask;
use app\common\model\human\HumanVoice;
use app\common\model\user\User;
use app\common\service\FileService;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use app\common\model\user\UserTokensLog;
use think\facade\Log;
class HumanLogic extends ApiLogic
{
const AVATAR_TRAINING = 'avatarTraining'; //形象训练
const VOICE_TRAINING = 'voiceTraining'; //音色训练
const AUDIO_TRAINING = 'audioTraining'; //文字转音频
const VIDEO_TRAINING = 'videoTraining'; //视频训练
const COPYWRITING_CREATE = 'copywritingCreate'; //文案创作
const AVATAR_TRAINING_PRO = 'avatarTrainingPro'; //形象训练
const VOICE_TRAINING_PRO = 'voiceTrainingPro'; //音色训练
const AUDIO_TRAINING_PRO = 'audioTrainingPro'; //文字转音频
const VIDEO_TRAINING_PRO = 'videoTrainingPro'; //视频训练
const AVATAR_TRAINING_YM = 'avatarTrainingYm'; //形象训练
const VOICE_TRAINING_YM = 'voiceTrainingYm'; //音色训练
const AUDIO_TRAINING_YM = 'audioTrainingYm'; //文字转音频
const VIDEO_TRAINING_YM = 'videoTrainingYm'; //视频训练
const AVATAR_TRAINING_YMT = 'avatarTrainingYmt'; //形象训练
const VOICE_TRAINING_YMT = 'voiceTrainingYmt'; //音色训练
const AUDIO_TRAINING_YMT = 'audioTrainingYmt'; //文字转音频
const VIDEO_TRAINING_YMT = 'videoTrainingYmt'; //视频训练
/**
* 更新形象
* @param array $data
* @param string $modelVersion
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function updateAnchor(array $data, string $modelVersion): bool
{
$model = HumanAnchor::where('model_version', $modelVersion)->where('status', 0);
if ($modelVersion == 1) {
$model = $model->where('anchor_id', $data['id']);
} elseif ($modelVersion == 2) {
// 目前是同步的 没有回调
return true;
} else {
return false;
}
$model->select()
->each(function ($item) use ($data) {
if ($item->model_version === 1) { //标准版
if (in_array($data['current_status'], ['completed', 'failed'])) {
$item->status = ($data['current_status'] == 'completed') ? 1 : 2;
// TODO 失败退费
if ($item->status == 2) {
self::refundTokens($item->user_id, $item->anchor_id, $item->task_id, 'human_anchor');
}
} else {
$item->status = 0;
}
} elseif ($item->model_version === 2) {
}
$item->save();
});
return true;
}
/**
* 更新音色
* @param array $data
* @param string $modelVersion
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function updateVoice(array $data, string $modelVersion): bool
{
//查询形象
$model = HumanVoice::where('model_version', $modelVersion)->where('status', 0);
if ($modelVersion == 1) {
$model = $model->where('voice_id', $data['id']);
} elseif ($modelVersion == 2) {
// 目前是同步的 没有回调
return true;
} else {
return false;
}
$model->select()
->each(function ($item) use ($data) {
if ($item->model_version === 1) { //标准版
if (in_array($data['current_status'], ['completed', 'failed'])) {
$item->status = ($data['current_status'] == 'completed') ? 1 : 2;
// TODO 失败退费
if ($item->status == 2) {
self::refundTokens($item->user_id, $item->anchor_id, $item->task_id, 'human_anchor');
}
} else {
$item->status = 0;
}
}
$item->save();
});
return true;
}
/**
* 更新音频
* @param array $data
* @param string $modelVersion
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function updateAudio(array $data, string $modelVersion): bool
{
//查询形象
$model = HumanAudio::where('model_version', $modelVersion)->where('status', 0);
if ($modelVersion == 1) {
$model = $model->where('audio_id', $data['id']);
} elseif ($modelVersion == 2) {
// 目前是同步的 没有回调
return true;
} else {
return false;
}
$model->select()
->each(function ($item) use ($data) {
if ($item->model_version === 1) { //标准版
$item->status = ($data['url'] != "") ? 1 : 2;
$item->url = FileService::downloadFileBySource($data['url'], 'audio');
// TODO 失败退费
if ($item->status == 2) {
self::refundTokens($item->user_id, $item->audio_id, $item->task_id, 'human_audio');
}
// 更新视频
HumanVideoTask::where('task_id', $item->task_id)->update([
'audio_url' => FileService::setFileUrl($item->url)
]);
}
$item->save();
});
return true;
}
/**
* 更新视频
* @param array $data
* @param string $modelVersion
* @return bool
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public static function updateVideo(array $data, string $modelVersion): bool
{
//查询形象
$model = HumanVideoTask::where('model_version', $modelVersion)->where('status', 0);
if ($modelVersion == 1) {
$model = $model->where('result_id', $data['id']);
} elseif (in_array($modelVersion,[4,6])) {
$model = $model->where('result_id', $data['job_id']);
}elseif ($modelVersion == 2) {
return true;
} else {
return false;
}
$model->select()
->each(function ($item) use ($data) {
if ($item->model_version === 1) { //标准版
if (in_array($data['current_status'], ['success', 'fail'])) {
$item->status = ($data['current_status'] == 'success') ? 1 : 2;
if ($item->status == 2) {
self::refundTokens($item->user_id, $item->result_id, $item->task_id, 'human_video');
}
} else {
$item->status = 0;
}
$item->result_url = FileService::downloadFileBySource($data['result'], 'video');
$item->remark = $data['msg'] ?? '';
}
if (in_array($item->model_version,[4,6])) { //高级版
//这里对应 status=3 或 status=4 3成功 4失败
if (in_array($data['status'], [3,4])) {
$item->status = ($data['status'] == 3) ? 1 : 2;
$scene = $item->model_version == 4 ? "human_video_ym" : "human_video_ymt";
if($item->status == 2){
self::refundTokens($item->user_id, $item->result_id, $item->task_id, $scene);
}
} else {
$item->status = 0;
}
$item->result_url = FileService::downloadFileBySource($data['video_Url'], 'video');
$item->remark = $data['msg'] ?? '';
}
$item->save();
});
return true;
}
/**
* @desc 退费
* @param int $userId
* @param int $id
* @param string $taskId
* @param string $type
* @return bool
*/
public static function refundTokens(int $userId, string $id, string $taskId, string $type): bool
{
try {
[$typeIndex, $typeID] = match ($type) {
'human_anchor' => [1, AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR],
'human_voice' => [2, AccountLogEnum::TOKENS_DEC_HUMAN_VOICE],
'human_audio' => [3, AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO],
'human_video' => [4, AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO],
'human_anchor_pro' => [1, AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR_PRO],
'human_voice_pro' => [2, AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_PRO],
'human_audio_pro' => [3, AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_PRO],
'human_video_pro' => [4, AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_PRO],
'human_anchor_ym' => [1, AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR_YM],
'human_voice_ym' => [2, AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YM],
'human_audio_ym' => [3, AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YM],
'human_video_ym' => [4, AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_YM],
'human_anchor_ymt' => [1, AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR_YMT],
'human_voice_ymt' => [2, AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YMT],
'human_audio_ymt' => [3, AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YMT],
'human_video_ymt' => [4, AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_YMT],
};
// 请求查询接口
$requestParams = [
'id' => $id,
'task_id' => $taskId,
'type' => $typeIndex
];
if (strpos($type, '_ymt') !== false) {
$response = \app\common\service\ToolsService::Human()->detailYmt($requestParams);
}elseif (strpos($type, '_ym') !== false) {
$response = \app\common\service\ToolsService::Human()->detailYm($requestParams);
}elseif (strpos($type, '_pro') !== false) {
$response = \app\common\service\ToolsService::Human()->detailPro($requestParams);
} else {
$response = \app\common\service\ToolsService::Human()->detail($requestParams);
}
if(isset($response['data']['task_status']) && $response['data']['task_status'] == 1) {
return true;
}
//查询是否已返还
if (UserTokensLog::where('user_id', $userId)->where('change_type', $typeID)->where('action', 1)->where('task_id', $taskId)->count() == 0) {
$points = UserTokensLog::where('user_id', $userId)->where('change_type', $typeID)->where('task_id', $taskId)->value('change_amount') ?? 0;
AccountLogLogic::recordUserTokensLog(false, $userId, $typeID, $points, $taskId);
}
return true;
} catch (\Throwable $e) {
return false;
}
}
/**
* @desc 视频生成任务
* @param $request
* @return bool
* @date 2024/9/30 17:55
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function videoTask($request)
{
try {
$anchor_id = $request['anchor_id'] ?? '';
$anchor_name = $request['anchor_name'] ?? '';
$name = $request['name'] ?? '';
$gender = $request['gender'] ?? 'male';
$pic = $request['pic'] ?? '';
$video_url = $request['video_url'] ?? '';
$msg = $request['msg'] ?? '';
$audio_url = $request['audio_url'] ?? '';
$model_version = $request['model_version'] ?? '';
$voice_id = $request['voice_id'] ?? '';
$voice_name = $request['voice_name'] ?? '';
$voice_type = $request['voice_type'] ?? 1;
if (empty($anchor_name) || empty($name) || !in_array($model_version, [1, 2, 4, 6]) || !in_array($gender, ['male', 'female'])) {
throw new \Exception('参数错误');
}
//生成一个唯一任务ID
$taskId = generate_unique_task_id();
// 驱动类型 1文字驱动 2音频驱动
$audio_type = $request['audio_type'] ?? 0;
switch ($audio_type) {
case 1: // 文字驱动
//模型1 字数超过150字节
if ($model_version == 1 && mb_strlen($msg) > 150) {
throw new \Exception('字数不能超过150字节');
}
//模型2 字数超过30000字节
if ($model_version == 2 && mb_strlen($msg) > 30000) {
throw new \Exception('字数不能超过30000字节');
}
//模型2 字数超过2000字
if (in_array($model_version, [4, 6]) && mb_strlen($msg, 'UTF-8') > 6200) {
throw new \Exception('字数不能超过2000');
}
break;
case 2: // 音频驱动
$msg = '';
if (in_array($model_version, [1])) {
throw new \Exception('目前不支持标准版');
}
// 音频驱动 音频链接不能为空
if (empty($audio_url)) {
throw new \Exception('音频文件不能为空');
}
break;
default:
throw new \Exception('参数错误');
}
//标题 只能有数字与字母、中文组成
$pattern = '/^[a-zA-Z0-9\x{4e00}-\x{9fa5}]*$/u';
if (!preg_match($pattern, $name)) {
throw new \Exception('标题只能有数字与字母、中文组成, 且10个字符以内');
}
// 验证余额
if ($model_version == 1) {
TokenLogService::checkToken(self::$uid, self::VIDEO_TRAINING);
} elseif ($model_version == 4) {
TokenLogService::checkToken(self::$uid, self::VIDEO_TRAINING_YM);
} elseif ($model_version == 6) {
TokenLogService::checkToken(self::$uid, self::VIDEO_TRAINING_YMT);
}else {
TokenLogService::checkToken(self::$uid, self::VIDEO_TRAINING_PRO);
}
//如果用户有任务在处理中,不允许创建任务
$taskCount = HumanVideoTask::where('user_id', self::$uid)
->where('status', 0)
->where('model_version', $model_version)
->count();
if ($taskCount >= 5) {
throw new \Exception('当前您已有5个任务正在排队处理中请等待任务完成后再创建');
}
// 形象ID 已有形象
if ($anchor_id) {
$anchor = HumanAnchor::where('anchor_id', $anchor_id)->findOrEmpty();
if ($anchor->isEmpty()) {
throw new \Exception('形象不存在');
}
$video_url = $anchor['url'];
}
if ( $pic == ''){
$pic = self::getVideoThumbnailFromUrl($video_url);
if ( $pic == false){
Log::write('获取图片任务失败' . $pic);
throw new \Exception('封面获取失败');
}
}
// 音色ID 已有音色
if ($voice_id && $voice_type == 1) {
$voice = HumanVoice::where('voice_id', $voice_id)->findOrEmpty();
if ($voice->isEmpty()) {
throw new \Exception('音色不存在');
}
$voice_name = $voice['name'];
}elseif ($voice_id && $voice_type == 0){
$voice_id = HumanVoice::getBuiltInVoice($voice_id,$model_version);
if ($voice_name == '') {
throw new \Exception('音色名称不能为空');
}
if ($voice_id === '00000') {
throw new \Exception('音色错误');
}
}
$videoTaskData = [
'user_id' => self::$uid,
'name' => $name,
'pic' => $pic,
'gender' => $gender,
'audio_type' => $audio_type,
'anchor_id' => $anchor_id,
'anchor_name' => $anchor_name,
'voice_id' => $voice_id,
'voice_name' => $voice_name,
'msg' => $msg,
'task_id' => $taskId,
'model_version' => $model_version,
'upload_video_url' => $video_url,
'upload_audio_url' => $audio_url,
];
if ($audio_type == 2) {
$videoTaskData['audio_url'] = $audio_url;
}
$videoTask = HumanVideoTask::create($videoTaskData);
self::$returnData = $videoTask->toArray();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @desc 创建形象
* @param $data
* @return bool
* @throws \Exception
* @date 2024/9/28 17:46
* @author dagouzi
*/
public static function createAnchor($data)
{
// 检查余额
if ($data['model_version'] == 1) {
TokenLogService::checkToken(self::$uid, 'human_anchor');
} elseif ($data['model_version'] == 4){
TokenLogService::checkToken(self::$uid, 'human_anchor_ym');
}elseif ($data['model_version'] == 6){
TokenLogService::checkToken(self::$uid, 'human_anchor_ymt');
}else {
TokenLogService::checkToken(self::$uid, 'human_anchor_pro');
}
$name = $data['name'] ?? '';
$gender = $data['gender'] ?? 'male';
$anchor_url = $data['url'] ?? '';
$model_version = $data['model_version'] ?? '';
if (empty($name) || !in_array($model_version, [1, 2, 4, 6]) || !in_array($gender, ['male', 'female']) || empty($anchor_url)) {
message('参数错误');
}
//标题 只能有数字与字母、中文组成
$pattern = '/^[a-zA-Z0-9\x{4e00}-\x{9fa5}]*$/u';
if (!preg_match($pattern, $name)) {
message('标题只能有数字与字母、中文组成, 且10个字符以内');
}
$pic = $data['pic'] ?? '';
if ($pic == ''){
$pic = self::getVideoThumbnailFromUrl($anchor_url);
if ( $pic == false){
self::setError('封面获取失败');
return false;
}
}
$taskId = generate_unique_task_id();
if (in_array($data['model_version'] ,[2, 4, 6])) {
$addData = [
'user_id' => self::$uid,
'status' => 1,
'anchor_id' => uniqid(),
'name' => $name,
'gender' => $gender,
'url' => $anchor_url,
'task_id' => $taskId,
'model_version' => $model_version,
'pic' => $pic,
];
$anchor = HumanAnchor::create($addData);
self::$returnData = [
'id' => $anchor->anchor_id,
'pic' => $pic,
'picurl' => FileService::getFileUrl($pic),
];
return true;
}
$request = [
'video_url' => $anchor_url,
'name' => $name,
'gender' => $gender,
];
switch ($model_version)
{
case 1:
$scene = self::AVATAR_TRAINING;
break;
case 2:
$scene = self::AVATAR_TRAINING_PRO;
break;
case 4:
$scene = self::AVATAR_TRAINING_YM;
break;
case 6:
$scene = self::AVATAR_TRAINING_YMT;
break;
default:
$scene = self::AVATAR_TRAINING;
break;
}
$result = self::requestUrl($request, $scene, self::$uid, $taskId);
if (!empty($result) && isset($result['id'])) {
$result['pic'] = $pic;
$result['picurl'] = FileService::getFileUrl($pic);
self::$returnData = $result;
$addData = [
'user_id' => self::$uid,
'status' => 0,
'anchor_id' => $result['id'],
'name' => $name,
'gender' => $gender,
'url' => $anchor_url,
'task_id' => $taskId,
'model_version' => $model_version,
'pic' => $pic
];
HumanAnchor::create($addData);
} else {
self::setError('合成失败');
return false;
}
return true;
}
/**
* @desc 形象重试
* @param $data
* @return bool
* @throws \Exception
* @date 2024/9/28 17:46
* @author dagouzi
*/
public static function anchorRetry(int $id)
{
$anchor = HumanAnchor::where('id', $id)->where('user_id', self::$uid)->findOrEmpty();
if ($anchor->isEmpty()) {
message('形象不存在');
}
if ($anchor->model_version == 2) {
message('当前模型无需重试');
}
//状态不对
if ($anchor->status != 2) {
message('当前任务不是失败状态,请勿提交');
}
//提交时间间隔1分钟
if (strtotime($anchor->update_time) > strtotime('-1 minute')) {
message('任务训练间隔1分钟请勿频繁提交');
}
//保存更新时间
$anchor->update_time = time();
$anchor->save();
$request = [
'video_url' => $anchor->url,
'name' => $anchor->name,
'gender' => $anchor->gender,
];
switch ( $anchor->model_version)
{
case 1:
$scene = self::AVATAR_TRAINING;
break;
case 2:
$scene = self::AVATAR_TRAINING_PRO;
break;
case 4:
$scene = self::AVATAR_TRAINING_YM;
break;
case 6:
$scene = self::AVATAR_TRAINING_YMT;
break;
default:
$scene = self::AVATAR_TRAINING;
break;
}
$result = self::requestUrl($request, $scene, $anchor->user_id, $anchor->task_id);
if (!empty($result) && isset($result['id'])) {
$anchor->anchor_id = $result['id'];
$anchor->status = 0;
$anchor->save();
self::$returnData = $anchor->toArray();
} else {
self::setError('重试失败');
return false;
}
return true;
}
/**
* @desc 形象列表
* @param $data
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @date 2024/9/28 18:25
* @author dagouzi
*/
public static function anchorLists($data): array
{
//TODO 新增分页
$pageNo = ($data['page_no'] - 1) * $data['page_size'];
$pageSize = $data['page_size'];
$modelVersion = $data['model_version'] ?? '';
$status = $data['status'] ?? '';
$name = $data['name'] ?? '';
$result = HumanAnchor::where(['user_id' => self::$uid])
->when($name, function ($query) use ($name) {
$query->where('name', 'like', '%' . $name . '%');
})
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', $modelVersion);
})
->when($status != "", function ($query) use ($status) {
$query->where('status', $status);
})
->limit($pageNo, $pageSize)
->order('create_time', 'desc')
->select()
->toArray();
$data = [
'lists' => $result,
'count' => HumanAnchor::where(['user_id' => self::$uid])
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', $modelVersion);
})
->count(),
'page_no' => $data['page_no'],
'page_size' => $data['page_size'],
];
return $data;
}
/**
* @desc 删除形象
* @param array $data
* @return bool
* @date 2024/9/28 18:31
* @author dagouzi
*/
public static function anchorDelete(array $data)
{
try {
if (is_string($data['id'])) {
HumanAnchor::destroy(['id' => $data['id'], 'user_id' => self::$uid]);
} else {
HumanAnchor::whereIn('id', $data['id'])->where('user_id', self::$uid)->select()->delete();
}
return true;
} catch (\Exception $exception) {
self::setError($exception->getMessage());
return false;
}
}
/**
* @desc 语音克隆
* @param $data
* @return bool
* @date 2024/9/28 18:36
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function createVoice($data)
{
// 检查余额
if ($data['model_version'] == 1) {
TokenLogService::checkToken(self::$uid, 'human_voice');
}elseif ($data['model_version'] == 4) {
TokenLogService::checkToken(self::$uid, 'human_voice_ym');
}elseif ($data['model_version'] == 6) {
TokenLogService::checkToken(self::$uid, 'human_voice_ymt');
}else {
TokenLogService::checkToken(self::$uid, 'human_voice_pro');
}
$name = $data['name'] ?? '';
$gender = $data['gender'] ?? '';
$voice_url = $data['url'] ?? '';
$model_version = $data['model_version'] ?? '';
if (empty($name) || !in_array($model_version, [1, 2, 4, 6]) || !in_array($gender, ['male', 'female']) || empty($voice_url)) {
message('参数错误');
}
//标题
$pattern = '/^[a-zA-Z0-9\p{Han}]{1,10}$/u';
if (!preg_match($pattern, $name)) {
message('标题只能有数字与字母、中文组成, 且10个字符以内');
}
$taskId = generate_unique_task_id();
$request = [
'name' => $name,
'gender' => $gender,
'audio_url' => $voice_url
];
switch ($model_version) {
case 1:
$scene = self::VOICE_TRAINING;
break;
case 2:
$scene = self::VOICE_TRAINING_PRO;
break;
case 4:
$scene = self::VOICE_TRAINING_YM;
break;
case 6:
$scene = self::VOICE_TRAINING_YMT;
break;
default:
$scene = self::VOICE_TRAINING_PRO;
break;
}
$result = self::requestUrl($request, $scene, self::$uid, $taskId);
if (!empty($result) && isset($result['id'])) {
self::$returnData = $result;
$addData = [
'user_id' => self::$uid,
'status' => $model_version == 2 ? 1 : 0,
'voice_id' => $result['id'],
'name' => $name,
'gender' => $gender,
'model_version' => $model_version,
'task_id' => $taskId,
'voice_urls' => $voice_url
];
HumanVoice::create($addData);
if(in_array( $model_version,[4,6])){
HumanTask::create([
'user_id' => self::$uid,
'video_task_id' => 0,
'task_id' => $taskId,
'model_version' => $model_version,
'data_id'=> $result['id'],
'type' => 2,
]);
}
} else {
self::setError('合成失败');
return false;
}
return true;
}
/**
* @desc 音色重试
* @param $data
* @return bool
* @throws \Exception
* @date 2024/9/28 17:46
* @author dagouzi
*/
public static function voiceRetry(int $id)
{
$voice = HumanVoice::where('id', $id)->where('user_id', self::$uid)->findOrEmpty();
if ($voice->isEmpty()) {
message('音色不存在');
}
//状态不对
if ($voice->status != 2) {
message('当前任务不是失败状态,请勿提交');
}
//提交时间间隔1分钟
if (strtotime($voice->update_time) > strtotime('-1 minute')) {
message('任务训练间隔1分钟请勿频繁提交');
}
//保存更新时间
$voice->update_time = time();
$voice->save();
if(!empty($voice->voice_urls)){
$request = [
'name' => $voice->name,
'gender' => $voice->gender,
'audio_url' => $voice->voice_urls
];
switch ($voice->model_version) {
case 1:
$scene = self::VOICE_TRAINING;
break;
case 2:
$scene = self::VOICE_TRAINING_PRO;
break;
case 4:
$scene = self::VOICE_TRAINING_YM;
break;
case 6:
$scene = self::VOICE_TRAINING_YMT;
break;
default:
$scene = self::VOICE_TRAINING_PRO;
break;
}
$result = self::requestUrl($request, $scene, $voice->user_id, $voice->task_id);
}else{
$result = [];
}
if (!empty($result) && isset($result['id'])) {
$voice->voice_id = $result['id'];
$voice->status = $voice->model_version == 2 ? 1 : 0;
if ($voice->model_version == 2) {
$voice->status = 1;
}
$voice->save();
// 更新音频
HumanAudio::where('task_id', $voice->task_id)->update([
'voice_id' => $result['id']
]);
self::$returnData = $voice->toArray();
} else {
self::setError('重试失败');
return false;
}
return true;
}
/**
* @desc 音色列表
* @param $data
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @date 2024/9/28 18:25
* @author dagouzi
*/
public static function voiceLists($data): array
{
//TODO 新增分页
$pageNo = ($data['page_no'] - 1) * $data['page_size'];
$pageSize = $data['page_size'];
$name = $data['name'] ?? '';
if (empty($data['model_version'])){
$modelVersion = '';
}else{
$modelVersion = json_decode($data['model_version'],true);
$modelVersion = is_array($modelVersion) ? $modelVersion : [(int)$modelVersion];
}
$status = $data['status'] ?? '';
$type = $data['type'] ?? 3;
$result = [];
$count = 0;
if (in_array($type,[1,3])) {
$result = HumanVoice::where(['user_id' => self::$uid])
->order('create_time', 'desc')
->limit($pageNo, $pageSize)
->when($name, function ($query) use ($name) {
$query->where('name', 'like', '%' . $name . '%');
})
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', 'in' ,$modelVersion);
})
->when($status != "", function ($query) use ($status) {
$query->where('status', $status);
})
->order('create_time', 'desc')
->select()->each(function ($item) {
$item->type = 1;
})
->toArray();
$count = HumanVoice::where(['user_id' => self::$uid])
->when($name, function ($query) use ($name) {
$query->where('name', 'like', '%' . $name . '%');
})
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', 'in' , $modelVersion);
})
->when($status != "", function ($query) use ($status) {
$query->where('status', $status);
})
->count();
}
if (in_array($type,[0,3])) {
$voice = HumanVoice::getModelList();
if ($voice) {
foreach ($voice['voice'] as &$v) {
$v['type'] = 0;
};
$result = array_merge($voice['voice'], $result);
}
$count = count( $result);
}
$data = [
'lists' => $result,
'count' => $count,
'page_no' => $data['page_no'],
'page_size' => $data['page_size'],
];
return $data;
}
/**
* @desc 删除音色
* @param $data
* @return bool
* @date 2024/9/28 18:31
* @author dagouzi
*/
public static function voiceDelete($data)
{
try {
if (is_string($data['id'])) {
HumanVoice::destroy(['id' => $data['id'], 'user_id' => self::$uid]);
} else {
HumanVoice::whereIn('id', $data['id'])->where('user_id', self::$uid)->select()->delete();
}
return true;
} catch (\Exception $exception) {
self::setError($exception->getMessage());
return false;
}
}
/**
* @desc 创建音频
* @param $data
* @return bool
* @date 2024/9/28 18:36
* @throws \GuzzleHttp\Exception\GuzzleException
* @author dagouzi
*/
public static function createAudio($data)
{
// 检查余额
if ($data['model_version'] == 1) {
TokenLogService::checkToken(self::$uid, 'human_audio');
}elseif ($data['model_version'] == 4){
TokenLogService::checkToken(self::$uid, 'human_audio_ym');
}elseif ($data['model_version'] == 6){
TokenLogService::checkToken(self::$uid, 'human_audio_ymt');
}else {
TokenLogService::checkToken(self::$uid, 'human_audio_pro');
}
$msg = $data['msg'] ?? '';
$name = $data['name'] ?? '';
$voice_id = $data['voice_id'] ?? '';
$model_version = $data['model_version'] ?? '';
if (empty($msg) || !in_array($model_version, [1, 2, 4,5,6]) || empty($voice_id)) {
message('参数错误');
}
//字数超过150字节
if ($model_version == 1 && mb_strlen($msg) > 150) {
message('字数不能超过150字节');
}
if ($model_version == 2 && mb_strlen($msg) > 30000) {
message('字数不能超过30000字节');
}
$request = [
'msg' => $msg,
'voice_id' => $voice_id
];
// 检查音色ID
$voice = HumanVoice::where('voice_id', $voice_id)->findOrEmpty();
if ($voice->status != 1) {
self::setError('请等待音色创建完成');
return false;
}
$taskId = generate_unique_task_id();
switch ($model_version) {
case 1:
$scene = self::AUDIO_TRAINING;
break;
case 2:
$scene = self::AUDIO_TRAINING_PRO;
break;
case 4:
$scene = self::AUDIO_TRAINING_YM;
break;
case 6:
$scene = self::AUDIO_TRAINING_YMT;
break;
default:
$scene = self::AUDIO_TRAINING_PRO;
break;
}
$result = self::requestUrl($request, $scene, self::$uid, $taskId);
if (!empty($result) && isset($result['url'])) {
self::$returnData = $result;
$addData = [
'user_id' => self::$uid,
'status' => 0,
'name' => $name,
'msg' => $msg,
'voice_id' => $voice_id,
'model_version' => $model_version,
'task_id' => $taskId,
'audio_id' => $result['id']
];
if ($model_version == 2) {
$addData['url'] = FileService::downloadFileBySource($result['url'], 'audio');
}
HumanAudio::create($addData);
} else {
self::setError('合成失败');
return false;
}
return true;
}
/**
* @desc 音频重试
* @param $data
* @return bool
* @throws \Exception
* @date 2024/9/28 17:46
* @author dagouzi
*/
public static function audioRetry(int $id)
{
$audio = HumanAudio::where('id', $id)->where('user_id', self::$uid)->findOrEmpty();
if ($audio->isEmpty()) {
message('音频不存在');
}
if (HumanVoice::where('voice_id', $audio->voice_id)->where('status', 1)->count() == 0) {
self::setError('请等待音色创建完成');
return false;
}
//状态不对
if ($audio->status != 2) {
message('当前任务不是失败状态,请勿提交');
}
//提交时间间隔1分钟
if (strtotime($audio->update_time) > strtotime('-1 minute')) {
message('任务训练间隔1分钟请勿频繁提交');
}
//保存更新时间
$audio->update_time = time();
$audio->save();
$request = [
'msg' => $audio->msg,
'voice_id' => $audio->voice_id
];
switch ($audio->model_version)
{
case 1:
$scene = self::AUDIO_TRAINING;
break;
case 2:
$scene = self::AUDIO_TRAINING_PRO;
break;
case 4:
$scene = self::AUDIO_TRAINING_YM;
break;
case 6:
$scene = self::AUDIO_TRAINING_YMT;
break;
default:
$scene = self::AUDIO_TRAINING_PRO;
break;
}
$result = self::requestUrl($request, $scene, $audio->user_id, $audio->task_id);
if ($audio->model_version == 2 && isset($result['url']) && $result['url']) {
$audio->url = FileService::downloadFileBySource($result['url'], 'audio');
$audio->status = 1;
$audio->save();
// 更新视频
HumanVideoTask::where('task_id', $audio->task_id)->update([
'audio_url' => FileService::setFileUrl($audio->url)
]);
} else if (isset($result['id']) && $result['id']) {
$audio->audio_id = $result['id'];
$audio->status = 0;
$audio->save();
} else {
self::setError('重试失败');
return false;
}
return true;
}
/**
* @desc 视频重试
* @param $data
* @return bool
* @throws \Exception
* @date 2024/9/28 17:46
* @author dagouzi
*/
public static function videoRetry(int $id)
{
$video = HumanVideoTask::where('id', $id)->where('user_id', self::$uid)->findOrEmpty();
if ($video->isEmpty()) {
message('视频不存在');
}
if ($video->anchor_id == "") {
self::setError('请等待形象创建完成');
return false;
}
if ($video->audio_url == "") {
self::setError('请等待音频创建完成');
return false;
}
//状态不对
if ($video->status != 2) {
message('当前任务不是失败状态,请勿提交');
}
//提交时间间隔1分钟
if (strtotime($video->update_time) > strtotime('-1 minute')) {
message('任务训练间隔1分钟请勿频繁提交');
}
//保存更新时间
$video->update_time = time();
$video->save();
$request = [
'name' => $video->name,
'avatar_id' => $video->anchor_id,
'video_url' => $video->upload_video_url,
'audio_url' => $video->audio_url
];
switch ($video->model_version)
{
case 1:
$scene = self::VIDEO_TRAINING;
break;
case 2:
$scene = self::VIDEO_TRAINING_PRO;
break;
case 4:
$scene = self::VIDEO_TRAINING_YM;
break;
case 6:
$scene = self::VIDEO_TRAINING_YMT;
break;
default:
$scene = self::VIDEO_TRAINING_PRO;
break;
}
$result = self::requestUrl($request, $scene, $video->user_id, $video->task_id);
if (!empty($result) && isset($result['id'])) {
$video->result_id = $result['id'];
$video->remark = "";
$video->status = 0;
$video->save();
self::$returnData = $video->toArray();
} else {
self::setError('重试失败');
return false;
}
return true;
}
/**
* @desc 音频列表
* @param $data
* @return array
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
* @date 2024/9/28 18:25
* @author dagouzi
*/
public static function audioLists($data): array
{
//TODO 新增分页
$pageNo = ($data['page_no'] - 1) * $data['page_size'];
$pageSize = $data['page_size'];
$name = $data['name'] ?? '';
$modelVersion = $data['model_version'] ?? '';
$status = $data['status'] ?? '';
$result = HumanAudio::where(['user_id' => self::$uid])
->order('create_time', 'desc')
->limit($pageNo, $pageSize)
->when($name, function ($query) use ($name) {
$query->where('name', 'like', '%' . $name . '%');
})
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', $modelVersion);
})
->when($status != "", function ($query) use ($status) {
$query->where('status', $status);
})
->order('create_time', 'desc')
->select()
->toArray();
$data = [
'lists' => $result,
'count' => HumanAudio::where(['user_id' => self::$uid])
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', $modelVersion);
})
->count(),
'page_no' => $data['page_no'],
'page_size' => $data['page_size'],
];
return $data;
}
/**
* @desc 删除音频
* @param $data
* @return bool
* @date 2024/9/28 18:31
* @author dagouzi
*/
public static function audioDelete($data)
{
try {
if (is_string($data['id'])) {
HumanAudio::destroy(['id' => $data['id'], 'user_id' => self::$uid]);
} else {
HumanAudio::whereIn('id', $data['id'])->where('user_id', self::$uid)->select()->delete();
}
return true;
} catch (\Exception $exception) {
self::setError($exception->getMessage());
return false;
}
}
/**
* @desc 视频列表
* @return array
* @date 2024/9/28 18:25
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author dagouzi
*/
public static function videoLists(array $data)
{
$modelVersion = $data['model_version'] ?? '';
$name = $data['name'] ?? '';
$status = $data['status'] ?? '';
$pageNo = ($data['page_no'] - 1) * $data['page_size'];
$pageSize = $data['page_size'];
$result = HumanVideoTask::where(['user_id' => self::$uid])
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', $modelVersion);
})
->when($name, function ($query) use ($name) {
$query->where('name', 'like', '%' . $name . '%');
})
->when($status != "", function ($query) use ($status) {
$query->where('status', $status);
})
->order('create_time', 'desc')
->limit($pageNo, $pageSize)
->select()
->toArray();
$data = [
'lists' => $result,
'count' => HumanVideoTask::where(['user_id' => self::$uid])
->when($modelVersion, function ($query) use ($modelVersion) {
$query->where('model_version', $modelVersion);
})
->when($name, function ($query) use ($name) {
$query->where('name', 'like', '%' . $name . '%');
})
->count(),
'page_no' => $data['page_no'],
'page_size' => $data['page_size'],
];
return $data;
}
/**
* @desc 删除视频
* @param $data
* @return bool
* @date 2024/9/28 18:31
* @author dagouzi
*/
public static function videoDelete($data)
{
try {
if (is_string($data['id'])) {
HumanVideoTask::destroy(['id' => $data['id'], 'user_id' => self::$uid]);
} else {
HumanVideoTask::whereIn('id', $data['id'])->where('user_id', self::$uid)->select()->delete();
}
return true;
} catch (\Exception $exception) {
self::setError($exception->getMessage());
return false;
}
}
/**
* @desc 视频信息定时任务
* @return bool
*/
public static function videoInfoCron(): bool
{
try {
HumanVideoTask::where('status', 0)
->where('result_id', '<>', '')
->where('model_version', '=', 2)
->order('tries', 'asc')
->limit(3)
->select()
->each(function ($item) {
try {
$response = \app\common\service\ToolsService::Human()->detailPro([
'id' => $item['result_id']
]);
//阿里极速版 2
// Log::write('阿里获取视频结果' . json_encode($response));
if (!empty($response['data']['url'])) {
$item->status = 1;
$item->tries = $item['tries'] + 1;
$item->result_url = FileService::downloadFileBySource($response['data']['url'], 'video');
$item->save();
return true;
}else{
$item->tries = $item['tries'] + 1;
$item->save();
return true;
}
} catch (\think\exception\HttpResponseException $e) {
Log::write('数字人视频保存失败' .$item['tries'].'----' . $e->getResponse()->getData()['msg']);
$item->remark = $e->getResponse()->getData()['msg'] ?? '';
$item->tries = $item['tries'] + 1;
$item->status = 2;
$item->save();
//退费
//查询是否已返还
if (UserTokensLog::where('user_id', $item->user_id)->where('change_type', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_PRO)->where('action', 1)->where('task_id', $item->task_id)->count() == 0) {
$points = UserTokensLog::where('user_id', $item->user_id)->where('change_type', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_PRO)->where('task_id', $item->task_id)->value('change_amount') ?? 0;
AccountLogLogic::recordUserTokensLog(false, $item->user_id, AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_PRO, $points, $item->task_id);
}
return true;
}
return true;
});
return true;
} catch (\Exception $e) {
Log::write('视频信息定时任务失败' . $e->getMessage());
return true;
}
}
/**
* @desc 视频定时任务
* @return bool|void
* @date 2024/10/2 10:36
* @throws \GuzzleHttp\Exception\GuzzleException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author dagouzi
*/
public static function videoTaskCron(string $taskId = '')
{
try {
if ($taskId == '') {
// 请求30分钟内还处于0状态的任务
HumanVideoTask::where('status', 0)
->where('result_id', '')
->where('create_time', '<=', strtotime('-30 minutes'))
->select()
->each(function ($item) {
self::setTimeoutTask($item->task_id);
});
}
// 第一步:获取任务列表
$taskModel = HumanVideoTask::where('status', 0)
->where('result_id', '')
->where('tries', '<', 3)
->limit(3);
if ($taskId) {
$taskModel = $taskModel->where('task_id', $taskId);
}
$modellist = HumanVoice::getModelList();
//第二步遍历任务
$taskModel->select()->each(function ($item) {
// var_dump('运行');
// //如果存在失败且还没到执行时间1分钟后执行
if ($item->tries >= 1 && strtotime($item->update_time) > strtotime('-1 minute')) {
return true;
}
try {
$tries = $item->tries;
//step1 形象
$anchor = HumanAnchor::where('user_id', $item['user_id'])->where('anchor_id', $item->anchor_id)->findOrEmpty();
// 如果形象不存在,则创建形象
if ($anchor->isEmpty()) {
$anchor = HumanAnchor::create([
'task_id' => $item->task_id,
'model_version' => $item->model_version,
'name' => $item->anchor_name,
'gender' => $item->gender,
'status' => 0,
'pic' => $item->pic,
'url' => $item->upload_video_url,
'user_id' => $item['user_id']
]);
}
// var_dump('人像训练');
// 如果没有训练,请求训练
if ($anchor->anchor_id == ""){
// var_dump('开始训练');
switch ($item->model_version) {
case 1:
$scene = self::AVATAR_TRAINING;
break;
case 2:
$scene = self::AVATAR_TRAINING_PRO;
break;
case 4:
$scene = self::AVATAR_TRAINING_YM;
break;
case 6:
$scene = self::AVATAR_TRAINING_YMT;
break;
default:
$scene = self::AVATAR_TRAINING_PRO;
break;
}
//请求形象接口
$response = self::requestUrl([
'name' => $anchor->name,
'gender' => $anchor->gender,
'video_url' => $anchor->url,
], $scene, $item->user_id, $item->task_id);
// var_dump( $response);
if (empty($response['id'])) {
message('形象创建失败');
}
if (in_array($item->model_version,[2,4,6])) {
$anchor->status = 1;
}
$anchor->anchor_id = $response['id'];
$anchor->save();
// 保存形象id
$item->anchor_id = $anchor->anchor_id;
$item->save();
}
// var_dump('请求完成');
// 形象还没有训练完成
if ($anchor->status != 1) {
return true;
}
// 文案驱动
if ($item->audio_type == 1) {
//step2 音色
$voice = HumanVoice::where('user_id', $item['user_id'])->where('voice_id', $item->voice_id)->findOrEmpty();
$voiceres = false;
// 如果音色不存在,则创建音色
if ($voice->isEmpty()) {
$BuiltInVoice = HumanVoice::getBuiltInVoiceList($item->model_version);
if (!in_array($item->voice_id, $BuiltInVoice)) {
$voice = HumanVoice::create([
'task_id' => $item->task_id,
'model_version' => $item->model_version,
'name' => $item->voice_name ? $item->voice_name : $item->name,
'gender' => $item->gender,
'voice_urls' => $item->upload_video_url,
'user_id' => $item['user_id']
]);
$voiceres = false;
}else{
$voice->voice_id = $item->voice_id;
$voiceres = true;
}
}
// var_dump('ys开始');
// 如果没有训练,请求训练
if ($voice->voice_id == "" && !$voiceres) {
// var_dump('创建音色');
switch ($item->model_version) {
case 1:
$scene = self::VOICE_TRAINING;
break;
case 2:
$scene = self::VOICE_TRAINING_PRO;
break;
case 4:
$scene = self::VOICE_TRAINING_YM;
$item->upload_video_url = FileService::getFileUrl($item->upload_video_url);
// var_dump( $item->upload_video_url );
break;
case 6:
$scene = self::VOICE_TRAINING_YMT;
$item->upload_video_url = FileService::getFileUrl($item->upload_video_url);
break;
default:
$scene = self::VOICE_TRAINING_PRO;
break;
}
$response = self::requestUrl([
'name' => $voice->name,
'gender' => $voice->gender,
'is_video' => true,
'audio_url' => $item->upload_video_url
], $scene, $item->user_id, $item->task_id);
// var_dump( $response );
if (empty($response['id'])) {
message('音色创建失败');
}
if ($item->model_version == 2) {
$voice->status = 1;
}
$voice->voice_id = $response['id'];
$voice->save();
$item->voice_id = $response['id'];
$item->save();
if(in_array($item->model_version,[4,6])){
$humantask = HumanTask::where([
'user_id'=> $item['user_id'],
'video_task_id' => $item->id,
'task_id' => $item->task_id,
'type' => 2,
'model_version' => $item->model_version,
])->findOrEmpty();
// 如果音色不存在,则创建音色
if ($humantask->isEmpty()) {
HumanTask::create([
'user_id' => $item['user_id'],
'video_task_id' => $item->id,
'task_id' => $item->task_id,
'model_version' => $item->model_version,
'data_id'=> $response['id'],
'type' => 2,
]);
$item->tries = 6;
$item->save();
}
}
}
// 音色还没有训练完成
if ($voice->status != 1 && !$voiceres) {
return true;
}
// var_dump('创建音频');
//step3 音频
$audio = HumanAudio::where('user_id', $item['user_id'])->where('task_id', $item->task_id)->findOrEmpty();
// 如果音频不存在,则创建音频
if ($audio->isEmpty()) {
$audio = HumanAudio::create([
'user_id' => $item['user_id'],
'task_id' => $item->task_id,
'model_version' => $item->model_version,
'name' => $item->name,
'msg' => $item->msg,
'voice_id' => $voice->voice_id
]);
}
// 如果没有训练,请求训练
if ($audio->audio_id == "") {
// var_dump('训练音频');
switch ($item->model_version) {
case 1:
$scene = self::AUDIO_TRAINING;
break;
case 2:
$scene = self::AUDIO_TRAINING_PRO;
break;
case 4:
$scene = self::AUDIO_TRAINING_YM;
break;
case 6:
$scene = self::AUDIO_TRAINING_YMT;
break;
default:
$scene = self::AUDIO_TRAINING_PRO;
break;
}
// 请求合成音频
$response = self::requestUrl([
'msg' => $item->msg,
'voice_id' => $voice->voice_id
], $scene, $item->user_id, $item->task_id);
//var_dump( $response );
// Log::write( $item->task_id .'高级版音频合成' . json_encode($response));
if ($item->model_version == 2) {
if (empty($response['url'])) {
message('音频创建失败');
}
$audio->audio_id = uniqid();
$audio->url = FileService::downloadFileBySource($response['url'], 'audio');
$audio->status = 1;
$audio->save();
} elseif(in_array($item->model_version,[4,6])){
if (empty($response['id'])) {
message('音频创建失败');
}
$audio->audio_id = $response['id'];
$audio->status = 0;
$audio->save();
$humantask = HumanTask::where([
'task_id' => $item->task_id,
'video_task_id' => $item->id,
'model_version' => $item->model_version,
'type' => 3,
'user_id'=> $item['user_id'],
])->findOrEmpty();
// var_dump('音频定时任务'.$humantask->isEmpty());
// 如果声音不存在,则创建
if ($humantask->isEmpty()) {
HumanTask::create([
'task_id' => $item->task_id,
'video_task_id' => $item->id,
'model_version' => $item->model_version,
'data_id'=> $response['id'],
'type' => 3,
'user_id' => $item['user_id']
]);
$item->tries = 6;
$item->save();
}
}else {
if (empty($response['id'])) {
message('音频创建失败');
}
$audio->audio_id = $response['id'];
$audio->save();
}
}
// 音频还没有训练完成
if ($audio->status != 1) {
return true;
}
// 保存音频url
$item->audio_url = $audio->url;
$item->save();
}
// var_dump('视频开始合成');
//最终合成视频 有形象了,有音频了
if ($item->anchor_id != "" && $item->audio_url != "") {
switch ($item->model_version) {
case 1:
$scene = self::VIDEO_TRAINING;
break;
case 2:
$scene = self::VIDEO_TRAINING_PRO;
break;
case 4:
$scene = self::VIDEO_TRAINING_YM;
break;
case 6:
$scene = self::VIDEO_TRAINING_YMT;
break;
default:
$scene = self::VIDEO_TRAINING_PRO;
break;
}
$response = self::requestUrl([
'name' => $item->name,
'avatar_id' => $item->anchor_id,
'video_url' => $item->upload_video_url,
'audio_url' => $item->audio_url
], $scene, $item->user_id, $item->task_id);
// Log::write( $item->task_id .'高级版视频合成' . json_encode($response));
if (empty($response['id'])) {
message('视频创建失败');
}
$item->result_id = $response['id'];
$item->save();
}
return true;
} catch (\think\exception\HttpResponseException $e) {
$item->tries = $tries + 1;
$item->save();
//失败3次 更新任务失败
if ($item->tries >= 3) {
$remark = $e->getResponse()->getData()['msg'] ?? '';
self::updateFailTask($item->task_id, $remark);
}
return true;
}
});
} catch (\Exception $e) {
Log::channel('human')->write('数字人错误:'.$e->getMessage());
return false;
}
return true;
}
/**
* @desc 数字人定时任务
* @return bool
*/
public static function humanInfoCron(): bool
{
try {
HumanTask::where('status', 0)
->whereIn('model_version',[4,6])
->where('tries', '<', 8)
->order('tries', 'asc')
->limit(3)
->select()
->each(function ($item) {
try {
$methodMap = [
4 => 'detailYm',
6 => 'detailYmt',
];
$method = $methodMap[$item['model_version']] ?? 'detailYmt';
if($item['type'] == 2){
// var_dump('获取音色'. $item['task_id']);
$response = \app\common\service\ToolsService::Human()->$method([
'type' => $item['type'],
'id' => $item['data_id']
]);
// Log::write('高级版获取音色结果' . json_encode($response));
if (isset($response['data']['status']) && $response['data']['status'] == 3) {
$item->result_id = $response['data']['train_id'];
$item->status = 1;
$item->tries = $item['tries'] + 1;
$item->result_url = $response['data']['demo_audio'];
$item->upload_url = FileService::downloadFileBySource($response['data']['demo_audio'], 'audio');
$item->save();
$task = HumanVoice::where([
'user_id'=> $item['user_id'],
'task_id'=> $item['task_id'],
'status' =>0
])->update([
'status' =>1,
'voice_urls'=> $item->upload_url
]);
// var_dump('音色更新'. $task);
$tt = HumanVideoTask::where([
'user_id'=> $item['user_id'],
'task_id'=> $item['task_id'],
'status' =>0
])->update([
'tries'=> 1
]);
// var_dump('任务重组'.$tt);
return true;
}else{
$item->tries = $item['tries'] + 1;
$item->save();
return true;
}
}
if($item['type'] == 3){
// var_dump('获取声音合成结果'. $item['task_id']);
$response = \app\common\service\ToolsService::Human()->$method([
'type' => $item['type'],
'id' => $item['data_id']
]);
// var_dump( $response);
//阿里极速版 2
if (isset($response['data']['status']) && $response['data']['status'] == 3) {
$upload_url = FileService::downloadFileBySource($response['data']['speech_url'], 'audio');
$item->result_id = $response['data']['id'];
$item->status = 1;
$item->tries = $item['tries'] + 1;
$item->result_url = $response['data']['speech_url'];
$item->upload_url = $upload_url;
$item->save();
$task = HumanAudio::where([
'user_id'=> $item['user_id'],
'task_id'=> $item['task_id'],
'status' =>0,
'audio_id'=> $response['data']['task_id'],
])->update([
'status' =>1,
'url' => $upload_url
]);
//var_dump('音色更新'. $task);
$tt = HumanVideoTask::where([
'user_id'=> $item['user_id'],
'task_id'=> $item['task_id'],
'status' =>0
])->update([
'tries'=> 1,
'audio_url' => $upload_url
]);
// var_dump('任务重组'.$tt);
return true;
}else{
$item->tries = $item['tries'] + 1;
$item->save();
return true;
}
}
} catch (\think\exception\HttpResponseException $e) {
Log::write('数字人保存失败' .$item['tries'].'----' . $e->getResponse()->getData()['msg']);
$item->remark = $e->getResponse()->getData()['msg'] ?? '';
$item->tries = $item['tries'] + 1;
$item->status = 2;
$item->save();
if($item->type == 2){
if($item->model_version == 4){
$scene = AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YM;
}else{
$scene = AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YMT;
}
//查询是否已返还
if (UserTokensLog::where('user_id', $item->user_id)->where('change_type', $scene)->where('action', 1)->where('task_id', $item->task_id)->count() == 0) {
$points = UserTokensLog::where('user_id', $item->user_id)->where('change_type', $scene)->where('task_id', $item->task_id)->value('change_amount') ?? 0;
AccountLogLogic::recordUserTokensLog(false, $item->user_id, $scene, $points, $item->task_id);
}
}else{
if($item->model_version == 4){
$scene = AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YM;
}else{
$scene = AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YMT;
}
if (UserTokensLog::where('user_id', $item->user_id)->where('change_type', $scene)->where('action', 1)->where('task_id', $item->task_id)->count() == 0) {
$points = UserTokensLog::where('user_id', $item->user_id)->where('change_type', $scene)->where('task_id', $item->task_id)->value('change_amount') ?? 0;
AccountLogLogic::recordUserTokensLog(false, $item->user_id, $scene, $points, $item->task_id);
}
}
//退费
return true;
}
return true;
});
return true;
} catch (\Exception $e) {
Log::write('视频信息定时任务失败' . $e->getMessage());
return true;
}
}
/**
* @desc 删除失败任务
* @param $taskId
* @return void
* @date 2024/10/2 10:43
* @author dagouzi
*/
private static function deleteFailTask(string $taskId): void
{
//删除音频失败任务, status=0,2
// TODO 不删除 让用户手动重试
// HumanAudio::where('task_id', $taskId)->whereIn('status', [0, 2])->findOrEmpty()->delete();
// //删除音色失败任务, status=0,2
// HumanVoice::where('task_id', $taskId)->whereIn('status', [0, 2])->findOrEmpty()->delete();
// //删除形象失败任务, status=0,2
// HumanAnchor::where('task_id', $taskId)->whereIn('status', [0, 2])->findOrEmpty()->delete();
// //删除视频失败任务, status=0,2
// HumanVideoTask::where('task_id', $taskId)->whereIn('status', [0, 2])->findOrEmpty()->delete();
}
/**
* @desc 删除失败任务
* @param string $taskId
* @param string $remark
* @return void
* @date 2024/10/2 10:43
* @author dagouzi
*/
private static function updateFailTask(string $taskId, string $remark): void
{
//更新音频失败任务, status=0,2
HumanAudio::where('task_id', $taskId)->where('status', 0)->update([
'status' => 2,
]);
//更新音色失败任务, status=0,2
HumanVoice::where('task_id', $taskId)->where('status', 0)->update([
'status' => 2,
]);
//更新形象失败任务, status=0,2
HumanAnchor::where('task_id', $taskId)->where('status', 0)->update([
'status' => 2,
]);
//更新视频失败任务, status=0,2
HumanVideoTask::where('task_id', $taskId)->where('status', 0)->update([
'status' => 2,
'remark' => $remark
]);
}
/**
* @desc 超时任务
* @param $taskId
* @return void
* @date 2024/10/2 10:43
* @author dagouzi
*/
private static function setTimeoutTask(string $taskId): void
{
$item = HumanVideoTask::where('task_id', $taskId)->findOrEmpty()->toArray();
// 任务超时
$Audio = HumanAudio::where('task_id', $taskId)->whereIn('status', 0)->update(['status' => 2]);
if ($Audio) {
switch ($item['model_version'])
{
case 1:
$scene = 'human_audio';
break;
case 2:
$scene = 'human_audio_pro';
break;
case 4:
$scene = 'human_audio_ym';
break;
case 6:
$scene = 'human_audio_ymt';
break;
default:
$scene = 'human_audio';
break;
}
self::refundTokens($item['user_id'], $item['audio_id'], $item['task_id'], $scene);
}
$Voice = HumanVoice::where('task_id', $taskId)->whereIn('status', 0)->update(['status' => 2]);
if ($Voice) {
switch ($item['model_version'])
{
case 1:
$scene = 'human_voice';
break;
case 2:
$scene = 'human_video_pro';
break;
case 4:
$scene = 'human_video_ym';
break;
case 6:
$scene = 'human_video_ymt';
break;
default:
$scene = 'human_video';
break;
}
self::refundTokens($item['user_id'], $item['voice_id'], $item['task_id'], $scene);
}
$Anchor = HumanAnchor::where('task_id', $taskId)->whereIn('status', 0)->update(['status' => 2]);
$Video = HumanVideoTask::where('task_id', $taskId)->whereIn('status', 0)->update(['status' => 2, 'remark' => '创作超时']);
if (!$Audio && !$Voice && !$Anchor && $Video) {
switch ($item['model_version'])
{
case 1:
$scene = 'human_video';
break;
case 2:
$scene = 'human_video_pro';
break;
case 4:
$scene = 'human_video_ym';
break;
case 6:
$scene = 'human_video_ymt';
break;
default:
$scene = 'human_video';
break;
}
self::refundTokens($item['user_id'], $item['result_id'], $item['task_id'], $scene);
}
}
/**
* 请求上游接口与计费
* @param array $request
* @param string $scene
* @param int $userId
* @param string $taskId
* @return array
* @throws \Exception
*/
private static function requestUrl(array $request, string $scene, int $userId, string $taskId): array
{
$requestService = \app\common\service\ToolsService::Human();
[$tokenScene, $tokenCode] = match ($scene) {
self::AUDIO_TRAINING => ['human_audio', AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO],
self::AVATAR_TRAINING => ['human_avatar', AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR],
self::VOICE_TRAINING => ['human_voice', AccountLogEnum::TOKENS_DEC_HUMAN_VOICE],
self::VIDEO_TRAINING => ['human_video', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO],
self::COPYWRITING_CREATE => ['copywriting_create', AccountLogEnum::TOKENS_DEC_HUMAN_COPYWRITING],
self::AUDIO_TRAINING_PRO => ['human_audio_pro', AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_PRO],
self::AVATAR_TRAINING_PRO => ['human_avatar_pro', AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR_PRO],
self::VOICE_TRAINING_PRO => ['human_voice_pro', AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_PRO],
self::VIDEO_TRAINING_PRO => ['human_video_pro', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_PRO],
self::AUDIO_TRAINING_YM => ['human_audio_ym', AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YM],
self::AVATAR_TRAINING_YM => ['human_avatar_ym', AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR_YM],
self::VOICE_TRAINING_YM => ['human_voice_ym', AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YM],
self::VIDEO_TRAINING_YM => ['human_video_ym', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_YM],
self::AUDIO_TRAINING_YMT => ['human_audio_ymt', AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YMT],
self::AVATAR_TRAINING_YMT => ['human_avatar_ymt', AccountLogEnum::TOKENS_DEC_HUMAN_AVATAR_YMT],
self::VOICE_TRAINING_YMT => ['human_voice_ymt', AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YMT],
self::VIDEO_TRAINING_YMT => ['human_video_ymt', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_YMT],
};
//计费
$unit = TokenLogService::checkToken($userId, $tokenScene);
// 添加辅助参数
$request['task_id'] = $taskId;
$request['user_id'] = $userId;
$request['now'] = time();
switch ($scene) {
case self::AVATAR_TRAINING:
$response = $requestService->avatarTraining($request);
break;
case self::VOICE_TRAINING:
$response = $requestService->voiceTraining($request);
break;
case self::AUDIO_TRAINING:
$response = $requestService->audioTraining($request);
break;
case self::VIDEO_TRAINING:
$response = $requestService->videoTraining($request);
break;
case self::COPYWRITING_CREATE:
$response = $requestService->copywritingCreate($request);
break;
case self::AVATAR_TRAINING_PRO:
$response = $requestService->avatarTrainingPro($request);
break;
case self::VOICE_TRAINING_PRO:
$response = $requestService->voiceTrainingPro($request);
break;
case self::AUDIO_TRAINING_PRO:
$response = $requestService->audioTrainingPro($request);
break;
case self::VIDEO_TRAINING_PRO:
$response = $requestService->videoTrainingPro($request);
break;
case self::AVATAR_TRAINING_YM:
$response = $requestService->avatarTrainingYm($request);
// var_dump( $response);
break;
case self::VOICE_TRAINING_YM:
$response = $requestService->voiceTrainingYm($request);
break;
case self::AUDIO_TRAINING_YM:
$response = $requestService->audioTrainingYm($request);
break;
case self::VIDEO_TRAINING_YM:
$response = $requestService->videoTrainingYm($request);
break;
case self::AVATAR_TRAINING_YMT:
$response = $requestService->avatarTrainingYmt($request);
break;
case self::VOICE_TRAINING_YMT:
$response = $requestService->voiceTrainingYmt($request);
break;
case self::AUDIO_TRAINING_YMT:
$response = $requestService->audioTrainingYmt($request);
break;
case self::VIDEO_TRAINING_YMT:
$response = $requestService->videoTrainingYmt($request);
break;
default:
}
//成功响应,需要扣费
if (isset($response['code']) && $response['code'] == 10000) {
$points = $unit;
if ($points > 0) {
$extra = [];
//合成视频按时长扣费
if (in_array($scene, [
self::AUDIO_TRAINING, self::VIDEO_TRAINING,
self::AUDIO_TRAINING_PRO, self::VIDEO_TRAINING_PRO,
self::AUDIO_TRAINING_YM, self::VIDEO_TRAINING_YM,
self::AUDIO_TRAINING_YMT, self::VIDEO_TRAINING_YMT
])) {
$duration = $response['data']['duration'] ?? 1;
$points = ceil($duration * $unit);
$extra = ['音视频时长' => $duration, '算力单价' => $unit, '实际消耗算力' => $points];
}
//token扣除
User::userTokensChange($userId, $points);
//记录日志
AccountLogLogic::recordUserTokensLog(true, $userId, $tokenCode, $points, $taskId, $extra);
}
}
return $response['data'] ?? [];
}
/**
* 请求上游接口与计费
* @param array $request
* @param string $scene
* @param int $userId
* @param string $taskId
* @return array
* @throws \Exception
*/
private static function tokeOutToken(string $scene, int $userId, string $taskId, string $id , $type = 0, $response = []): bool
{
$requestService = \app\common\service\ToolsService::Human();
[$tokenScene, $tokenCode] = match ($scene) {
self::AUDIO_TRAINING_YM => ['human_audio_ym', AccountLogEnum::TOKENS_DEC_HUMAN_AUDIO_YM],
self::VOICE_TRAINING_YM => ['human_voice_ym', AccountLogEnum::TOKENS_DEC_HUMAN_VOICE_YM],
self::VIDEO_TRAINING_YM => ['human_video_ym', AccountLogEnum::TOKENS_DEC_HUMAN_VIDEO_YM],
};
//计费
$unit = TokenLogService::checkToken($userId, $tokenScene);
// 添加辅助参数
$request['task_id'] = $taskId;
$request['user_id'] = $userId;
$request['id'] = $id;
$request['type'] = $type;
$request['now'] = time();
if($response == []){
switch ($scene) {
case self::VOICE_TRAINING_YM:
$response = $requestService->detailYm($request);
break;
case self::AUDIO_TRAINING_YM:
$response = $requestService->detailYm($request);
break;
case self::VIDEO_TRAINING_YM:
$response = $requestService->detailYm($request);
break;
default:
}
}
//成功响应,需要扣费
if (isset($response['code']) && $response['code'] == 10000) {
if($scene == self::VIDEO_TRAINING_YM){
$response['data']['status'] = $response['data']['task_status'] ?? 0;
}
$points = $unit;
if ($points > 0 && $response['data']['status'] == 3) {
$extra = [];
//合成视频按时长扣费
if (in_array($scene, [
self::AUDIO_TRAINING_YM, self::VIDEO_TRAINING_YM
])) {
$duration = $response['data']['duration'] ?? 1;
$points = ceil($duration * $unit);
$extra = ['音视频时长' => $duration, '算力单价' => $unit, '实际消耗算力' => $points];
}
//token扣除
User::userTokensChange($userId, $points);
//记录日志
AccountLogLogic::recordUserTokensLog(true, $userId, $tokenCode, $points, $taskId, $extra);
}
return true;
}
return false;
}
/**
* 保存base64文件
*/
public static function saveBase64File(string $base64String, string $type)
{
$typePath = match ($type) {
'avatar' => 'images',
'audio' => 'audio',
'video' => 'video',
};
$savePath = public_path('uploads/' . $typePath . '/' . date('Ymd'));
// 检查保存路径是否存在,不存在则创建
if (!is_dir($savePath)) {
mkdir($savePath, 0777, true);
}
// 获取 MIME 类型和 Base64 数据
if (preg_match('/^data:image\/(\w+);base64,/', $base64String, $matches)) {
$mimeType = $matches[1]; // 获取 MIME 类型(如 png、jpeg、gif 等)
$base64String = substr($base64String, strpos($base64String, ',') + 1); // 移除 Base64 前缀
} else {
return "";
}
// 检查支持的 MIME 类型并获取对应的文件后缀
$extensionMap = [
// 视频类型
'mp4' => '.mp4',
'avi' => '.avi',
'mov' => '.mov',
'wmv' => '.wmv',
'flv' => '.flv',
'mkv' => '.mkv',
// 音频类型
'mp3' => '.mp3',
'wav' => '.wav',
'aac' => '.aac',
'ogg' => '.ogg',
'flac' => '.flac',
// 图片类型
'png' => '.png',
'jpeg' => '.jpg',
'jpg' => '.jpg',
'gif' => '.gif',
'bmp' => '.bmp',
'webp' => '.webp'
];
$fileExtension = $extensionMap[$mimeType] ?? '.bin'; // 未知类型默认为 .bin
// 解码 Base64 数据
$imageData = base64_decode($base64String);
// 检查解码是否成功
if ($imageData === false) {
return "";
}
$finalFileName = generate_unique_task_id() . $fileExtension;
// 保存图片到文件
$filePath = $savePath . $finalFileName;
if (file_put_contents($filePath, $imageData)) {
return FileService::getFileUrl(str_replace(public_path(), '', $filePath));
} else {
return "";
}
}
public static function copywriting($data){
$keywords = $data['keywords'] ?? '';
$number = $data['number'] ?? '';
if (empty($keywords) || empty($number)) {
message('参数错误');
}
$taskId = generate_unique_task_id();
$request = [
'keywords' => $keywords,
'number' => $number,
];
$scene = self::COPYWRITING_CREATE;
$result = self::requestUrl($request, $scene, self::$uid, $taskId);
if (!empty($result) && isset($result['content'])) {
self::$returnData = $result;
} else {
self::setError('生成失败');
return false;
}
return true;
}
public static function getVideoThumbnailFromUrl($videoUrl, $time = '00:00:01')
{
try {
// 生成缩略图保存路径
$dirPath = public_path() . 'uploads/images/' . date('Ymd') . '/';
$thumbnailname = date('YmdHis') . substr(md5($videoUrl), 0, 5)
. str_pad(rand(0, 9999), 4, '0', STR_PAD_LEFT) . '.jpg';
$thumbnailPath = $dirPath . $thumbnailname;
// 检查文件夹是否存在,不存在则创建
if (!file_exists($dirPath)) {
mkdir($dirPath, 0777, true); // 0777 是权限模式true 表示递归创建
}
// 构建 FFmpeg 命令,直接使用视频的 URL
$command = "ffmpeg -i " . escapeshellarg($videoUrl) . " -ss " . escapeshellarg($time) . " -vframes 1 " . escapeshellarg($thumbnailPath) . " 2>&1";
// 执行命令
$output = shell_exec($command);
// 检查是否成功生成缩略图
if (file_exists($thumbnailPath)) {
return 'uploads/images/' . date('Ymd') . '/' . $thumbnailname;
} else {
return false;
}
} catch (\Exception $e) {
Log::write('获取图片任务失败11' . $e->getMessage());
return false;
}
}
}