瀏覽代碼

优化导出pdf功能

lh 1 月之前
父節點
當前提交
2caebe2871
共有 1 個文件被更改,包括 103 次插入19 次删除
  1. 103 19
      app/Services/Anime/AnimeService.php

+ 103 - 19
app/Services/Anime/AnimeService.php

@@ -9380,6 +9380,8 @@ class AnimeService
         $anime = DB::table('mp_animes')->where('id', $animeId)->where('is_deleted', 0)->first();
         if (!$anime) Utils::throwError('20003:该动漫不存在!');
         $anime = (array)$anime;
+        // 剧本名去掉末尾"_数字"标识后缀,只保留剧名本身
+        $anime['anime_name'] = trim((string)preg_replace('/_\d+$/u', '', getProp($anime, 'anime_name', '')));
 
         // 获取分集(默认展示版本,按集数升序)
         $episodeQuery = DB::table('mp_anime_episodes')
@@ -9425,6 +9427,13 @@ class AnimeService
             }
             ksort($acts);
 
+            // 分镜序号按出现先后重新编号(整集范围内保持连续,兼容【镜头N】/【分镜N】两种格式)
+            $segmentCounter = 0;
+            foreach ($acts as &$act) {
+                $act['content'] = $this->renumberSegmentBlocks($act['content'], $segmentCounter);
+            }
+            unset($act);
+
             // 旁白音色:优先取分集roles中"旁白"的voice_prompt,否则从片段内容解析
             $narratorVoice = '';
             $episodeRoles = json_decode(getProp($episode, 'roles', ''), true) ?: [];
@@ -9760,33 +9769,81 @@ class AnimeService
         $image = @imagecreatefromstring($content);
         if ($image === false) return null;
 
-        $image = $this->resizeImageForPdf($image, 2048);
-        $jpeg = dirname($rawFile) . '/anime_pdf_img_' . md5($url) . '.jpg';
-        $blank = imagecreatetruecolor(imagesx($image), imagesy($image));
-        $white = imagecolorallocate($blank, 255, 255, 255);
-        imagefill($blank, 0, 0, $white);
-        imagecopy($blank, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
-        $saved = imagejpeg($blank, $jpeg, 85);
+        $jpeg = $this->compressImageForPdf($image, $rawFile, $url);
         imagedestroy($image);
-        imagedestroy($blank);
-        if ($saved) {
+        if ($jpeg !== null) {
             @unlink($rawFile);
-            return $jpeg;
         }
-        return null;
+        return $jpeg;
     }
 
     /**
-     * 图片等比缩放(限制最大边,降低内存与PDF体积)
-     * @param resource $image GD图片资源
+     * 图片压缩:白底合成后转JPEG,分级缩小尺寸与降低质量,保证每张不超过2MB
+     * @param resource $srcImage GD图片资源
+     * @param string $rawFile 原始图片临时文件
+     * @param string $url 图片地址(用于命名)
+     * @return string|null 压缩后的JPEG文件路径
+     */
+    private function compressImageForPdf($srcImage, $rawFile, $url) {
+        $maxBytes = 2 * 1024 * 1024; // 2MB
+        $sizes = [2048, 1600, 1280, 1024, 800];
+        $qualities = [85, 70, 55, 40];
+        $jpeg = dirname($rawFile) . '/anime_pdf_img_' . md5($url) . '.jpg';
+
+        foreach ($sizes as $size) {
+            $resized = $this->copyAndResize($srcImage, $size);
+            $flat = $this->flattenToWhite($resized);
+            imagedestroy($resized);
+
+            foreach ($qualities as $quality) {
+                ob_start();
+                $saved = imagejpeg($flat, null, $quality);
+                $data = ob_get_clean();
+                if ($saved && $data !== false && $data !== '' && strlen($data) <= $maxBytes) {
+                    file_put_contents($jpeg, $data);
+                    imagedestroy($flat);
+                    dLog('anime')->info('导出PDF图片压缩完成', [
+                        'url' => $url,
+                        'size' => filesize($jpeg),
+                        'edge' => $size,
+                        'quality' => $quality,
+                    ]);
+                    return $jpeg;
+                }
+            }
+            imagedestroy($flat);
+        }
+
+        // 兜底:最小尺寸、最低质量强制输出
+        $resized = $this->copyAndResize($srcImage, 800);
+        $flat = $this->flattenToWhite($resized);
+        imagedestroy($resized);
+        $saved = imagejpeg($flat, $jpeg, 30);
+        imagedestroy($flat);
+        if (!$saved) return null;
+
+        dLog('anime')->info('导出PDF图片压缩完成(兜底)', [
+            'url' => $url,
+            'size' => filesize($jpeg),
+        ]);
+        return $jpeg;
+    }
+
+    /**
+     * 等比缩放图片副本(不销毁原图)
+     * @param resource $srcImage GD图片资源
      * @param int $maxEdge 最大边长
      * @return resource
      */
-    private function resizeImageForPdf($image, $maxEdge) {
-        $width = imagesx($image);
-        $height = imagesy($image);
+    private function copyAndResize($srcImage, $maxEdge) {
+        $width = imagesx($srcImage);
+        $height = imagesy($srcImage);
         $max = max($width, $height);
-        if ($max <= $maxEdge) return $image;
+        if ($max <= $maxEdge) {
+            $copy = imagecreatetruecolor($width, $height);
+            imagecopy($copy, $srcImage, 0, 0, 0, 0, $width, $height);
+            return $copy;
+        }
 
         $ratio = $maxEdge / $max;
         $newWidth = max(1, (int)round($width * $ratio));
@@ -9794,9 +9851,36 @@ class AnimeService
         $resized = imagecreatetruecolor($newWidth, $newHeight);
         imagealphablending($resized, false);
         imagesavealpha($resized, true);
-        imagecopyresampled($resized, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
-        imagedestroy($image);
+        imagecopyresampled($resized, $srcImage, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
         return $resized;
     }
+
+    /**
+     * 将GD图片合成到白色背景上(处理透明通道,输出RGB)
+     * @param resource $image GD图片资源
+     * @return resource
+     */
+    private function flattenToWhite($image) {
+        $width = imagesx($image);
+        $height = imagesy($image);
+        $flat = imagecreatetruecolor($width, $height);
+        $white = imagecolorallocate($flat, 255, 255, 255);
+        imagefill($flat, 0, 0, $white);
+        imagecopy($flat, $image, 0, 0, 0, 0, $width, $height);
+        return $flat;
+    }
+
+    /**
+     * 将片段内容中的分镜序号按出现先后重新编号(保证整集连续)
+     * @param string $content 片段内容
+     * @param int $counter 当前已编号数量(引用传递,整集累计)
+     * @return string
+     */
+    private function renumberSegmentBlocks($content, &$counter) {
+        return preg_replace_callback('/^【(分镜|镜头)\d+】/mu', function ($match) use (&$counter) {
+            $counter++;
+            return '【' . $match[1] . $counter . '】';
+        }, $content);
+    }
 }