AnimeCheckImgUrlCommand.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. <?php
  2. namespace App\Console\Anime;
  3. use Illuminate\Console\Command;
  4. use Illuminate\Support\Facades\DB;
  5. use App\Services\AIGeneration\AIImageGenerationService;
  6. class AnimeCheckImgUrlCommand extends Command
  7. {
  8. /**
  9. * The name and signature of the console command.
  10. *
  11. * @var string
  12. */
  13. protected $signature = 'Anime:checkImgUrl';
  14. /**
  15. * The console command description.
  16. *
  17. * @var string
  18. */
  19. protected $description = '检查动漫对话角色和场景图片生成状态';
  20. protected $aiImageGenerationService;
  21. public function __construct(AIImageGenerationService $aiImageGenerationService)
  22. {
  23. parent::__construct();
  24. $this->aiImageGenerationService = $aiImageGenerationService;
  25. }
  26. /**
  27. * Execute the console command.
  28. *
  29. * @return int
  30. */
  31. public function handle()
  32. {
  33. dLog('command')->info('====================开始检查动漫对话角色和场景图片生成状态====================');
  34. $time_start = microtime(true);
  35. $timeout = 50; // 超时时间50秒
  36. $check_interval = 3; // 每3秒检查一次
  37. while (true) {
  38. // 检查是否超时
  39. $elapsed_time = microtime(true) - $time_start;
  40. if ($elapsed_time >= $timeout) {
  41. dLog('command')->info('检查超时,退出循环');
  42. break;
  43. }
  44. // 检查是否有需要处理的数据
  45. $has_pending_animes = $this->hasPendingAnimes();
  46. $has_pending_episodes = $this->hasPendingEpisodes();
  47. // 如果没有需要检查的内容,直接跳出循环
  48. if (!$has_pending_animes && !$has_pending_episodes) {
  49. dLog('command')->info('没有需要检查的内容,退出循环');
  50. break;
  51. }
  52. // 检查动漫全局角色和场景
  53. if ($has_pending_animes) {
  54. $this->checkAnimes();
  55. }
  56. // 检查剧集角色和场景
  57. if ($has_pending_episodes) {
  58. $this->checkEpisodes();
  59. }
  60. // 等待3秒后继续下一次检查
  61. sleep($check_interval);
  62. }
  63. $time_end = microtime(true);
  64. dLog('command')->info('脚本执行时间: '.round($time_end-$time_start, 2).'秒');
  65. dLog('command')->info('====================结束检查动漫对话角色和场景图片生成状态====================');
  66. }
  67. // 检查动漫全局执行中的任务
  68. private function hasPendingAnimes() {
  69. $time_expire_datetime = date('Y-m-d H:i:s', strtotime('-12 hours'));
  70. DB::table('mp_animes')->where('generate_status', '执行中')->where('generate_at', '<', $time_expire_datetime)->update([
  71. 'generate_status' => '失败',
  72. 'updated_at'=>date('Y-m-d H:i:s'),
  73. ]);
  74. return DB::table('mp_animes')->where('generate_status', '执行中')->exists();
  75. }
  76. // 检查剧集执行中的任务
  77. private function hasPendingEpisodes() {
  78. $time_expire_datetime = date('Y-m-d H:i:s', strtotime('-12 hours'));
  79. DB::table('mp_anime_episodes')->whereIn('generate_status', ['执行中'])->where('generate_at', '<', $time_expire_datetime)->update([
  80. 'generate_status' => '失败',
  81. 'updated_at'=>date('Y-m-d H:i:s'),
  82. ]);
  83. return DB::table('mp_anime_episodes')->whereIn('generate_status', ['执行中', '待执行'])->exists();
  84. }
  85. // 检查全局
  86. private function checkAnimes() {
  87. dLog('command')->info('====================开始检查动漫全局====================');
  88. // 获取待执行的动漫对话数据
  89. $data = DB::table('mp_animes')->where('generate_status', '执行中')->select('id', 'roles', 'scenes')->get();
  90. foreach ($data as $item) {
  91. $id = getProp($item, 'id');
  92. $roles = json_decode(getProp($item, 'roles'), true);
  93. $scenes = json_decode(getProp($item, 'scenes'), true);
  94. $role_count = count($roles);
  95. $scene_count = count($scenes);
  96. // 处理角色图片状态
  97. $role_result = $this->checkRoles($id, $roles);
  98. // 处理场景图片状态
  99. $scene_result = $this->checkScenes($id, $scenes);
  100. if ($role_result['success_count'] == $role_count && $scene_result['success_count'] == $scene_count) {
  101. // 同步更新该动漫对话剧集的所有角色或场景
  102. $this->syncRolesAndScenes($id, $role_result['roles'], $scene_result['scenes']);
  103. }
  104. }
  105. dLog('command')->info('====================结束检查动漫全局====================');
  106. }
  107. // 检查全局角色
  108. private function checkRoles($id, $roles) {
  109. $role_success_count = 0;
  110. foreach ($roles as &$role) {
  111. $task_id = getProp($role, 'task_id');
  112. $task_status = getProp($role, 'task_status');
  113. if (!$task_id || in_array($task_status, ['success', 'failed']) || getProp($role, 'role') == '旁白') {
  114. $role_success_count++;
  115. continue;
  116. }
  117. $task = DB::table('mp_generate_pic_tasks')->where('id', $task_id)->select('result_url', 'status')->first();
  118. $status = getProp($task, 'status');
  119. if (in_array($status, ['success', 'failed'])) {
  120. $role['task_status'] = $status; // 使用实际的任务状态
  121. $role['url'] = '';
  122. $result_url = getProp($task, 'result_url');
  123. if ($result_url && is_json($result_url) && $status == 'success') {
  124. $role['url'] = json_decode($result_url, true)[0];
  125. }
  126. $role_success_count++;
  127. }
  128. }
  129. if ($role_success_count > 0) {
  130. DB::table('mp_animes')->where('id', $id)->update(['roles' => json_encode($roles, 256)]);
  131. }
  132. return [
  133. 'roles' => $roles,
  134. 'success_count' => $role_success_count,
  135. ];
  136. }
  137. // 检查全局场景
  138. private function checkScenes($id, $scenes) {
  139. $scene_success_count = 0;
  140. foreach ($scenes as &$scene) {
  141. $task_id = getProp($scene, 'task_id');
  142. $task_status = getProp($scene, 'task_status');
  143. if (!$task_id || in_array($task_status, ['success', 'failed'])) {
  144. $scene_success_count++;
  145. continue;
  146. }
  147. $task = DB::table('mp_generate_pic_tasks')->where('id', $task_id)->select('result_url', 'status')->first();
  148. $status = getProp($task, 'status');
  149. if (in_array($status, ['success', 'failed'])) {
  150. $scene['task_status'] = $status; // 使用实际的任务状态
  151. $scene['url'] = '';
  152. $result_url = getProp($task, 'result_url');
  153. if ($result_url && is_json($result_url) && $status == 'success') {
  154. $scene['url'] = json_decode($result_url, true)[0];
  155. }
  156. $scene_success_count++;
  157. }
  158. }
  159. if ($scene_success_count > 0) {
  160. DB::table('mp_animes')->where('id', $id)->update(['scenes' => json_encode($scenes, 256)]);
  161. }
  162. return [
  163. 'scenes' => $scenes,
  164. 'success_count' => $scene_success_count,
  165. ];
  166. }
  167. // 剧集中同步继承全局角色和场景
  168. private function syncRolesAndScenes($id, $roles, $scenes) {
  169. // 获取该动漫对话对应的所有剧集
  170. $episodes = DB::table('mp_anime_episodes')->where('anime_id', $id)->get();
  171. if ($episodes->isEmpty()) {
  172. dLog('command')->info("动漫对话ID:{$id} 没有关联的剧集");
  173. return;
  174. }
  175. foreach ($episodes as $episode) {
  176. $episode_id = getProp($episode, 'id');
  177. $episode_roles = json_decode(getProp($episode, 'roles'), true) ?: [];
  178. $episode_scenes = json_decode(getProp($episode, 'scenes'), true) ?: [];
  179. $roles_updated = false;
  180. $scenes_updated = false;
  181. // 更新剧集中的角色URL
  182. foreach ($episode_roles as &$episode_role) {
  183. $episode_role_name = getProp($episode_role, 'role');
  184. $episode_description = getProp($episode_role, 'description');
  185. $episode_url = getProp($episode_role, 'url');
  186. $episode_task_id = getProp($episode_role, 'task_id');
  187. // 如果已有URL或已有任务ID,跳过
  188. if (!empty($episode_url) || !empty($episode_task_id)) {
  189. continue;
  190. }
  191. $url_inherited = false;
  192. // 遍历方法中的角色数据
  193. foreach ($roles as $role) {
  194. $role_name = getProp($role, 'role');
  195. $role_description = getProp($role, 'description');
  196. $role_url = getProp($role, 'url');
  197. $role_task_id = getProp($role, 'task_id');
  198. $role_task_status = getProp($role, 'task_status');
  199. // 满足三个条件:1.角色名相同 2.描述相同 3.剧集中URL为空且方法中URL不为空
  200. if ($episode_role_name == $role_name
  201. && $episode_description == $role_description
  202. && empty($episode_url)
  203. && !empty($role_url)) {
  204. // 只继承 task_id、task_status 和 url,不继承其他创建图片的参数
  205. $episode_role['task_id'] = $role_task_id;
  206. $episode_role['task_status'] = $role_task_status;
  207. $episode_role['url'] = $role_url;
  208. $roles_updated = true;
  209. $url_inherited = true;
  210. dLog('command')->info("剧集ID:{$episode_id} 角色:{$role_name} 继承全局数据 (task_id:{$role_task_id}, task_status:{$role_task_status}, url:{$role_url})");
  211. break;
  212. }
  213. }
  214. // 如果无法继承URL,则创建图片生成任务
  215. if (!$url_inherited && $episode_role_name != '旁白') {
  216. try {
  217. // 获取剧集的美术风格
  218. $art_style = getProp($episode, 'art_style');
  219. $description = $episode_description;
  220. if ($art_style) {
  221. $description = "美术风格:\n{$art_style}\n主体描述:{$description}\n图片要求:图片侧重点在于主体人物信息,需要突出人物形象,不显示场景或背景信息";
  222. }
  223. $params = [
  224. 'prompt' => $description,
  225. 'ref_img_urls' => []
  226. ];
  227. $task = $this->aiImageGenerationService->createImageGenerationTask($params);
  228. $task_id = $task->id;
  229. if ($task_id) {
  230. $episode_role['task_id'] = $task_id;
  231. $episode_role['task_status'] = 'processing';
  232. $roles_updated = true;
  233. dLog('command')->info("剧集ID:{$episode_id} 角色:{$episode_role_name} 创建图片生成任务,任务ID:{$task_id}");
  234. }
  235. } catch (\Exception $e) {
  236. dLog('command')->error("剧集ID:{$episode_id} 角色:{$episode_role_name} 创建图片生成任务失败: " . $e->getMessage());
  237. }
  238. }
  239. }
  240. // 更新剧集中的场景URL
  241. foreach ($episode_scenes as &$episode_scene) {
  242. $episode_scene_name = getProp($episode_scene, 'scene');
  243. $episode_description = getProp($episode_scene, 'description');
  244. $episode_url = getProp($episode_scene, 'url');
  245. $episode_task_id = getProp($episode_scene, 'task_id');
  246. // 如果已有URL或已有任务ID,跳过
  247. if (!empty($episode_url) || !empty($episode_task_id)) {
  248. continue;
  249. }
  250. $url_inherited = false;
  251. // 遍历方法中的场景数据
  252. foreach ($scenes as $scene) {
  253. $scene_name = getProp($scene, 'scene');
  254. $scene_description = getProp($scene, 'description');
  255. $scene_url = getProp($scene, 'url');
  256. $scene_task_id = getProp($scene, 'task_id');
  257. $scene_task_status = getProp($scene, 'task_status');
  258. // 满足三个条件:1.场景名相同 2.描述相同 3.剧集中URL为空且方法中URL不为空
  259. if ($episode_scene_name == $scene_name
  260. && $episode_description == $scene_description
  261. && empty($episode_url)
  262. && !empty($scene_url)) {
  263. // 只继承 task_id、task_status 和 url,不继承其他创建图片的参数
  264. $episode_scene['task_id'] = $scene_task_id;
  265. $episode_scene['task_status'] = $scene_task_status;
  266. $episode_scene['url'] = $scene_url;
  267. $scenes_updated = true;
  268. $url_inherited = true;
  269. dLog('command')->info("剧集ID:{$episode_id} 场景:{$scene_name} 继承全局数据 (task_id:{$scene_task_id}, task_status:{$scene_task_status}, url:{$scene_url})");
  270. break;
  271. }
  272. }
  273. // 如果无法继承URL,则创建图片生成任务
  274. if (!$url_inherited) {
  275. try {
  276. // 获取剧集的美术风格
  277. $art_style = getProp($episode, 'art_style');
  278. $description = $episode_description;
  279. if ($art_style) {
  280. $description = "美术风格:\n{$art_style}\n场景描述:{$description}\n图片要求: 图片侧重点是场景,生成的是场景环境图片,不要出现人物";
  281. }
  282. $params = [
  283. 'prompt' => $description,
  284. 'ref_img_urls' => []
  285. ];
  286. $task = $this->aiImageGenerationService->createImageGenerationTask($params);
  287. $task_id = $task->id;
  288. if ($task_id) {
  289. $episode_scene['task_id'] = $task_id;
  290. $episode_scene['task_status'] = 'processing';
  291. $scenes_updated = true;
  292. dLog('command')->info("剧集ID:{$episode_id} 场景:{$episode_scene_name} 创建图片生成任务,任务ID:{$task_id}");
  293. }
  294. } catch (\Exception $e) {
  295. dLog('command')->error("剧集ID:{$episode_id} 场景:{$episode_scene_name} 创建图片生成任务失败: " . $e->getMessage());
  296. }
  297. }
  298. }
  299. // 如果有更新,则保存到数据库
  300. if ($roles_updated || $scenes_updated) {
  301. $update_data = ['updated_at' => date('Y-m-d H:i:s')];
  302. if ($roles_updated) {
  303. $update_data['roles'] = json_encode($episode_roles, 256);
  304. }
  305. if ($scenes_updated) {
  306. $update_data['scenes'] = json_encode($episode_scenes, 256);
  307. }
  308. // 继承URL或创建任务后,将状态改为待执行,等后续检查分集图片成功后再改为成功状态
  309. $update_data['generate_status'] = '待执行';
  310. $update_data['generate_at'] = date('Y-m-d H:i:s');
  311. DB::table('mp_anime_episodes')->where('id', $episode_id)->update($update_data);
  312. dLog('command')->info("剧集ID:{$episode_id} 数据已同步更新,状态改为待执行");
  313. }
  314. }
  315. // 更新状态为已完成
  316. DB::table('mp_animes')->where('id', $id)->update(['generate_status' => '已完成', 'updated_at'=>date('Y-m-d H:i:s')]);
  317. dLog('command')->info('动漫对话图片已全部生成完毕');
  318. }
  319. // 检查剧集
  320. private function checkEpisodes() {
  321. dLog('command')->info('====================开始检查剧集====================');
  322. // 获取待执行和执行中的剧集数据
  323. $data = DB::table('mp_anime_episodes')->whereIn('generate_status', ['待执行', '执行中'])->select('id', 'roles', 'scenes', 'generate_status')->get();
  324. foreach ($data as $item) {
  325. $id = getProp($item, 'id');
  326. $roles = json_decode(getProp($item, 'roles'), true);
  327. $scenes = json_decode(getProp($item, 'scenes'), true);
  328. $role_count = count($roles);
  329. $scene_count = count($scenes);
  330. $current_status = getProp($item, 'generate_status');
  331. // 如果状态是待执行,先改为执行中
  332. if ($current_status == '待执行') {
  333. DB::table('mp_anime_episodes')->where('id', $id)->update([
  334. 'generate_status' => '执行中',
  335. 'generate_at' => date('Y-m-d H:i:s'),
  336. 'updated_at' => date('Y-m-d H:i:s')
  337. ]);
  338. dLog('command')->info("剧集ID:{$id} 状态从待执行改为执行中");
  339. }
  340. // 处理角色图片状态
  341. $role_result = $this->checkEpisodeRoles($id, $roles);
  342. // 处理场景图片状态
  343. $scene_result = $this->checkEpisodeScenes($id, $scenes);
  344. if ($role_result['success_count'] == $role_count && $scene_result['success_count'] == $scene_count) {
  345. // 更新状态为已完成
  346. DB::table('mp_anime_episodes')->where('id', $id)->update(['generate_status' => '已完成', 'updated_at'=>date('Y-m-d H:i:s')]);
  347. dLog('command')->info("剧集ID:{$id} 图片已全部生成完毕,状态改为已完成");
  348. }
  349. }
  350. dLog('command')->info('====================结束检查剧集====================');
  351. }
  352. // 检查剧集角色
  353. private function checkEpisodeRoles($id, $roles) {
  354. $role_success_count = 0;
  355. foreach ($roles as &$role) {
  356. $task_id = getProp($role, 'task_id');
  357. $task_status = getProp($role, 'task_status');
  358. if (!$task_id || in_array($task_status, ['success', 'failed']) || getProp($role, 'role') == '旁白') {
  359. $role_success_count++;
  360. continue;
  361. }
  362. $task = DB::table('mp_generate_pic_tasks')->where('id', $task_id)->select('result_url', 'status')->first();
  363. $status = getProp($task, 'status');
  364. if (in_array($status, ['success', 'failed'])) {
  365. $role['task_status'] = $status; // 使用实际的任务状态
  366. $role['url'] = '';
  367. $result_url = getProp($task, 'result_url');
  368. if ($result_url && is_json($result_url) && $status == 'success') {
  369. $role['url'] = json_decode($result_url, true)[0];
  370. }
  371. $role_success_count++;
  372. }
  373. }
  374. if ($role_success_count > 0) {
  375. DB::table('mp_anime_episodes')->where('id', $id)->update(['roles' => json_encode($roles, 256)]);
  376. }
  377. return [
  378. 'roles' => $roles,
  379. 'success_count' => $role_success_count,
  380. ];
  381. }
  382. // 检查剧集场景
  383. private function checkEpisodeScenes($id, $scenes) {
  384. $scene_success_count = 0;
  385. foreach ($scenes as &$scene) {
  386. $task_id = getProp($scene, 'task_id');
  387. $task_status = getProp($scene, 'task_status');
  388. if (!$task_id || in_array($task_status, ['success', 'failed'])) {
  389. $scene_success_count++;
  390. continue;
  391. }
  392. $task = DB::table('mp_generate_pic_tasks')->where('id', $task_id)->select('result_url', 'status')->first();
  393. $status = getProp($task, 'status');
  394. if (in_array($status, ['success', 'failed'])) {
  395. $scene['task_status'] = $status; // 使用实际的任务状态
  396. $scene['url'] = '';
  397. $result_url = getProp($task, 'result_url');
  398. if ($result_url && is_json($result_url) && $status == 'success') {
  399. $scene['url'] = json_decode($result_url, true)[0];
  400. }
  401. $scene_success_count++;
  402. }
  403. }
  404. if ($scene_success_count > 0) {
  405. DB::table('mp_anime_episodes')->where('id', $id)->update(['scenes' => json_encode($scenes, 256)]);
  406. }
  407. return [
  408. 'scenes' => $scenes,
  409. 'success_count' => $scene_success_count,
  410. ];
  411. }
  412. }