SegmentCheckAudioCommand.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <?php
  2. namespace App\Console\Anime;
  3. use Illuminate\Console\Command;
  4. use Illuminate\Support\Facades\DB;
  5. use GuzzleHttp\Client;
  6. class SegmentCheckAudioCommand extends Command
  7. {
  8. /**
  9. * The name and signature of the console command.
  10. *
  11. * @var string
  12. */
  13. protected $signature = 'Segment:checkAudio';
  14. /**
  15. * The console command description.
  16. *
  17. * @var string
  18. */
  19. protected $description = '检查分镜音频信息';
  20. public function __construct()
  21. {
  22. parent::__construct();
  23. }
  24. /**
  25. * Execute the console command.
  26. *
  27. * @return int
  28. */
  29. public function handle()
  30. {
  31. dLog('command')->info('====================开始检查分镜音频信息====================');
  32. $time_start = microtime(true);
  33. $timeout = 50; // 超时时间50秒
  34. $check_interval = 3; // 每3秒检查一次
  35. while (true) {
  36. // 检查是否超时
  37. $elapsed_time = microtime(true) - $time_start;
  38. if ($elapsed_time >= $timeout) {
  39. dLog('command')->info('检查超时,退出循环');
  40. break;
  41. }
  42. // 1. 查询mp_dub_video_tasks表中1分钟之前创建的generate_status=执行中的任务,并重新调用API
  43. $oneMinuteAgo = date('Y-m-d H:i:s', strtotime('-1 minute'));
  44. $pendingTasks = DB::table('mp_dub_video_tasks')
  45. ->where('generate_status', '执行中')
  46. ->where('created_at', '<=', $oneMinuteAgo)
  47. ->limit(10) // 每次处理10个任务
  48. ->get();
  49. if ($pendingTasks->isNotEmpty()) {
  50. dLog('command')->info('找到待重试的音频任务', ['count' => $pendingTasks->count()]);
  51. foreach ($pendingTasks as $task) {
  52. try {
  53. $dubTaskId = $task->id;
  54. sleep(1);
  55. // 请求远程服务器执行生成
  56. $client = new Client(['timeout' => 300, 'verify' => false]);
  57. $result = $client->get("http://122.9.129.83:5000/api/anime/genAudio?taskId={$dubTaskId}");
  58. $response = $result->getBody()->getContents();
  59. $response_arr = json_decode($response, true);
  60. if (!isset($response_arr['code']) || (int)$response_arr['code'] !== 0) {
  61. $error_msg = isset($response_arr['msg']) ? $response_arr['msg'] : '未知错误';
  62. dLog('command')->error('火山生成音频失败', ['error' => $error_msg, 'task_id' => $dubTaskId]);
  63. logDB('command', 'error', '火山生成音频失败', ['error' => $error_msg, 'task_id' => $dubTaskId]);
  64. // 标记任务为失败
  65. DB::table('mp_dub_video_tasks')
  66. ->where('id', $dubTaskId)
  67. ->update([
  68. 'generate_status' => '失败',
  69. 'updated_at' => date('Y-m-d H:i:s')
  70. ]);
  71. } else {
  72. dLog('command')->info('音频任务重试成功', ['task_id' => $dubTaskId]);
  73. }
  74. } catch (\Exception $e) {
  75. dLog('command')->error('重试音频任务异常', [
  76. 'task_id' => $task->id,
  77. 'error' => $e->getMessage()
  78. ]);
  79. }
  80. }
  81. }
  82. // 2. 查询mp_episode_segments表中img_url不为空但audio_url为空的分镜
  83. $segmentsNeedAudio = DB::table('mp_episode_segments')
  84. ->whereNotNull('img_url')
  85. ->where('img_url', '!=', '')
  86. ->where('voice_type', '!=', '')
  87. ->where(function($query) {
  88. $query->whereNull('audio_url')
  89. ->orWhere('audio_url', '');
  90. })
  91. ->limit(10) // 每次处理2个分镜
  92. ->get();
  93. if ($segmentsNeedAudio->isNotEmpty()) {
  94. dLog('command')->info('找到需要生成音频的分镜', ['count' => $segmentsNeedAudio->count()]);
  95. foreach ($segmentsNeedAudio as $segment) {
  96. try {
  97. $segment_id = $segment->segment_id;
  98. $dialogue = getProp($segment, 'dialogue');
  99. $now = date('Y-m-d H:i:s');
  100. // 如果有对话内容,创建视频配音合成任务
  101. if (!empty($dialogue)) {
  102. $generate_json = [
  103. 'text' => $dialogue,
  104. 'role' => getProp($segment, 'voice_actor'),
  105. 'voice_type' => getProp($segment, 'voice_type'),
  106. 'voice_name' => getProp($segment, 'voice_name'),
  107. 'emotion' => getProp($segment, 'emotion'),
  108. 'emotion_type' => getProp($segment, 'emotion_type'),
  109. 'gender' => getProp($segment, 'gender'),
  110. 'speed_ratio' => getProp($segment, 'speed_ratio', 0),
  111. 'loudness_ratio' => getProp($segment, 'loudness_ratio', 0),
  112. 'emotion_scale' => getProp($segment, 'emotion_scale', 0),
  113. 'pitch' => getProp($segment, 'pitch', 0),
  114. ];
  115. // 插入视频配音合成任务
  116. $dubTaskId = DB::table('mp_dub_video_tasks')->insertGetId([
  117. 'alias_segment_id' => $segment_id,
  118. 'generate_status' => '执行中',
  119. 'audio_url' => '',
  120. 'generate_json' => json_encode($generate_json, 256),
  121. 'created_at' => $now,
  122. 'updated_at' => $now,
  123. ]);
  124. if (!$dubTaskId) {
  125. dLog('command')->error('创建配音任务失败', ['segment_id' => $segment_id]);
  126. continue;
  127. }
  128. // 先更新分镜表,将audio_url设置为空字符串(标记正在生成)
  129. DB::table('mp_episode_segments')
  130. ->where('segment_id', $segment_id)
  131. ->update([
  132. 'audio_url' => '',
  133. 'updated_at' => $now
  134. ]);
  135. dLog('command')->info('开始请求远程服务器生成音频', ['task_id' => $dubTaskId, 'segment_id' => $segment_id]);
  136. sleep(1);
  137. // 请求远程服务器执行生成(远程服务器会更新audio_url字段)
  138. $client = new Client(['timeout' => 300, 'verify' => false]);
  139. $result = $client->get("http://122.9.129.83:5000/api/anime/genAudio?taskId={$dubTaskId}");
  140. $response = $result->getBody()->getContents();
  141. $response_arr = json_decode($response, true);
  142. if (!isset($response_arr['code']) || (int)$response_arr['code'] !== 0) {
  143. $error_msg = isset($response_arr['msg']) ? $response_arr['msg'] : '未知错误';
  144. dLog('command')->error('火山生成音频失败', ['error' => $error_msg, 'task_id' => $dubTaskId, 'segment_id' => $segment_id]);
  145. logDB('command', 'error', '火山生成音频失败', ['error' => $error_msg, 'task_id' => $dubTaskId, 'segment_id' => $segment_id]);
  146. } else {
  147. dLog('command')->info('音频任务创建成功', ['task_id' => $dubTaskId, 'segment_id' => $segment_id]);
  148. }
  149. } else {
  150. // dialogue为空时,直接写入默认音频信息
  151. DB::table('mp_episode_segments')
  152. ->where('segment_id', $segment_id)
  153. ->update([
  154. 'audio_url' => 'https://zw-audiobook.tos-cn-beijing.volces.com/effects/ellipses_2s.mp3',
  155. 'audio_duration' => 2.0,
  156. 'subtitle_info' => json_encode(['duration' => 2.0, 'words' => []], 256),
  157. 'updated_at' => $now
  158. ]);
  159. dLog('command')->info('分镜无对话,写入默认音频', ['segment_id' => $segment_id]);
  160. }
  161. } catch (\Exception $e) {
  162. dLog('command')->error('处理分镜音频异常', [
  163. 'segment_id' => $segment->segment_id,
  164. 'error' => $e->getMessage()
  165. ]);
  166. }
  167. }
  168. }
  169. // 如果没有待处理的任务,退出循环
  170. if ($pendingTasks->isEmpty() && $segmentsNeedAudio->isEmpty()) {
  171. dLog('command')->info('没有待处理的音频任务,退出循环');
  172. break;
  173. }
  174. // 等待3秒后继续下一次检查
  175. sleep($check_interval);
  176. }
  177. $time_end = microtime(true);
  178. $execution_time = round($time_end - $time_start, 2);
  179. dLog('command')->info('脚本执行时间: '.round($time_end-$time_start, 2).'秒');
  180. dLog('command')->info('====================结束检查分镜音频信息====================');
  181. }
  182. }