ActShowContent.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <?php
  2. namespace App\Libs;
  3. use Illuminate\Support\Facades\DB;
  4. /**
  5. * act_show_content 处理:把动漫画风提示词插入每个「画面:[…]」块
  6. *
  7. * 规则:
  8. * 1. 取 mp_animes.art_style_type,查 mp_art_styles(is_deleted=0)的 prompt;
  9. * 2. prompt 非空时,插入到每个「画面:」后第一个闭合 ] 之前,用中文逗号分隔;
  10. * 3. 找不到闭合 ] 的块、已包含该画风提示词的块跳过。
  11. *
  12. * 受 config('anime.act_show_art_style_enabled') 总开关控制,默认关闭;
  13. * 只处理 act_show_content 字段,不修改 act_content。
  14. */
  15. class ActShowContent
  16. {
  17. /** 画风提示词缓存(anime_id => prompt),同一次请求内避免重复查库 */
  18. private static $stylePromptCache = [];
  19. /**
  20. * 把画风提示词插入 act_show_content 中每个「画面:[…]」块
  21. *
  22. * @param string $content act_show_content 内容
  23. * @param int $animeId 所属动漫ID
  24. * @return string 处理后的内容(开关关闭、无画风提示词时原样返回)
  25. */
  26. public static function insertArtStylePrompt($content, $animeId)
  27. {
  28. if (!config('anime.act_show_art_style_enabled', false)) {
  29. return $content;
  30. }
  31. if (!is_string($content) || $content === '' || strpos($content, '画面:') === false) {
  32. return $content;
  33. }
  34. $prompt = self::getArtStylePrompt($animeId);
  35. if ($prompt === '') {
  36. return $content;
  37. }
  38. $huamianLen = strlen('画面:');
  39. $insertPositions = [];
  40. $skipped = 0;
  41. $offset = 0;
  42. while (($pos = strpos($content, '画面:', $offset)) !== false) {
  43. $searchStart = $pos + $huamianLen;
  44. $closePos = strpos($content, ']', $searchStart);
  45. if ($closePos === false) {
  46. // 该「画面:」块找不到闭合 ],跳过
  47. $skipped++;
  48. break;
  49. }
  50. // 闭合 ] 之前已经出现下一个「画面:」,说明当前块没有闭合 ],跳过当前块
  51. $nextPos = strpos($content, '画面:', $searchStart);
  52. if ($nextPos !== false && $nextPos < $closePos) {
  53. $skipped++;
  54. $offset = $nextPos;
  55. continue;
  56. }
  57. $inner = substr($content, $searchStart, $closePos - $searchStart);
  58. // 画面块内没有 [,] 或已经跨到其它字段行,说明不是标准的「画面:[…]」,跳过
  59. if (strpos($inner, '[') === false
  60. || preg_match('/(^|\n)\s*(运镜|配音台词|背景音效|出场角色|场景|出场道具|【(?:镜头|分镜))/u', $inner)) {
  61. $skipped++;
  62. $offset = $closePos + 1;
  63. continue;
  64. }
  65. if (strpos($inner, $prompt) !== false) {
  66. // 已包含画风提示词,避免重复插入
  67. $skipped++;
  68. } else {
  69. $insertPositions[] = [
  70. 'pos' => $closePos,
  71. 'needComma' => !preg_match('/[,,、]\s*$/u', $inner),
  72. ];
  73. }
  74. $offset = $closePos + 1;
  75. }
  76. if ($insertPositions) {
  77. // 从后往前插入,避免前面的插入影响后面的位置
  78. foreach (array_reverse($insertPositions) as $item) {
  79. $insertText = ($item['needComma'] ? ',' : '') . $prompt;
  80. $content = substr($content, 0, $item['pos']) . $insertText . substr($content, $item['pos']);
  81. }
  82. dLog('anime')->debug('act_show_content 插入画风提示词', [
  83. 'anime_id' => (int)$animeId,
  84. 'inserted' => count($insertPositions),
  85. 'skipped' => $skipped,
  86. ]);
  87. } elseif ($skipped > 0) {
  88. dLog('anime')->warning('act_show_content 未找到可插入的「画面:[…]」块', [
  89. 'anime_id' => (int)$animeId,
  90. 'skipped' => $skipped,
  91. ]);
  92. }
  93. return $content;
  94. }
  95. /**
  96. * 按动漫ID取画风提示词(mp_animes.art_style_type -> mp_art_styles.prompt)
  97. *
  98. * @param int $animeId
  99. * @return string 取不到时返回空字符串
  100. */
  101. private static function getArtStylePrompt($animeId)
  102. {
  103. $animeId = (int)$animeId;
  104. if ($animeId <= 0) {
  105. return '';
  106. }
  107. if (!array_key_exists($animeId, self::$stylePromptCache)) {
  108. $prompt = '';
  109. $anime = DB::table('mp_animes')->where('id', $animeId)->first(['art_style_type']);
  110. $artStyleType = $anime ? trim((string)$anime->art_style_type) : '';
  111. if ($artStyleType !== '') {
  112. $styleRow = DB::table('mp_art_styles')
  113. ->where('art_style', $artStyleType)
  114. ->where('is_deleted', 0)
  115. ->first(['prompt']);
  116. $prompt = $styleRow ? trim((string)$styleRow->prompt) : '';
  117. }
  118. self::$stylePromptCache[$animeId] = $prompt;
  119. }
  120. return self::$stylePromptCache[$animeId];
  121. }
  122. }