Преглед изворни кода

1.新增压缩图片尺寸功能2.文生图优化参考图功能,并支持多张

lh пре 4 месеци
родитељ
комит
05b3fd142b

+ 10 - 0
app/Console/Test/TestCommand.php

@@ -5,6 +5,7 @@ namespace App\Console\Test;
 use App\Cache\UserCache;
 use App\Facade\Site;
 use App\Libs\TikTok\Kernel\Support\Str;
+use App\Models\MpGeneratePicTask;
 use App\Services\DeepSeek\DeepSeekService;
 use getID3;
 use GuzzleHttp\Client;
@@ -49,6 +50,15 @@ class TestCommand extends Command
      */
     public function handle()
     {
+        // $url = 'https://zw-audiobook.tos-cn-beijing.volces.com/image/ai_generation_1772525682_69a69872e3a87..png';
+        $url = 'https://zw-audiobook.tos-cn-beijing.volces.com/image/ai_generation_1772525682_69a69872e3a87..png';
+        $compressed_url = compressRemoteImageUrlToSize($url);
+        // $compressed_url = compressRemoteImageUrlToSizeImagick($url);
+        $filename = 'ai_generation_1772525682_69a69872e3a87.png';
+        $url = uploadStreamByTos('image', $compressed_url, $filename);
+        // $url = file_put_contents($filename, $compressed_url);
+        dd($url);
+
         // // 转存阿里云oss文件到tos
         // $audio_effects = DB::table('mp_timbres')->where('id', 246)->get();
         // $total = count($audio_effects);

+ 137 - 4
app/Http/Controllers/AIGeneration/ImageGenerationController.php

@@ -31,13 +31,17 @@ class ImageGenerationController extends BaseController
     public function createTask(Request $request): JsonResponse
     {
         $data = $request->all();
-        $validator = Validator::make($data, [
+
+        // 首先验证基本参数
+        $baseRules = [
             'prompt' => 'required|string|max:2000',
             'width' => 'required|numeric|between:1024,4096',
             'height' => 'required|numeric|between:1024,4096',
             'image_num' => 'required|numeric|between:1,4',
             'scale' => 'required|numeric|between:0,100',
-        ], [
+        ];
+
+        $baseMessages = [
             'prompt.required' => '提示词不能为空',
             'prompt.max' => '提示词不能超过2000个字符',
             'width.required' => '宽不能为空',
@@ -52,11 +56,111 @@ class ImageGenerationController extends BaseController
             'scale.required' => '缩放比例不能为空',
             'scale.numeric' => '缩放比例必须是数字',
             'scale.between' => '缩放比例必须在0到100之间',
-        ]);
+        ];
+
+        // 根据传入参数决定验证规则
+        if (isset($data['ref_img_url'])) {
+            // URL 方式 - 支持单个或多个URL(逗号分隔或数组)
+            $validationRules = array_merge($baseRules, [
+                'ref_img_url' => 'required',
+            ]);
+            
+            $validationMessages = array_merge($baseMessages, [
+                'ref_img_url.required' => '参考图片URL不能为空',
+            ]);
+        } else {
+            // 上传文件方式 - 支持多个文件
+            $validationRules = array_merge($baseRules, [
+                'ref_img_file' => 'required',
+                'ref_img_file.*' => 'image|mimes:jpeg,png|max:4800',
+            ]);
+            
+            $validationMessages = array_merge($baseMessages, [
+                'ref_img_file.required' => '参考图片不能为空',
+                'ref_img_file.*.image' => '参考图片必须是图片文件',
+                'ref_img_file.*.mimes' => '参考图片必须是jpeg,png格式',
+                'ref_img_file.*.max' => '参考图片大小不能超过4.7MB',
+            ]);
+        }
+        
+        $validator = Validator::make($data, $validationRules, $validationMessages);
+        
         if ($validator->fails()) {
             Utils::throwError('1002:'.$validator->errors()->first());
         }
 
+        $refImgUrls = [];
+        $refImgBase64List = [];
+
+        // 处理上传的图片
+        if ($request->hasFile('ref_img_file')) {
+            $refImgFiles = is_array($request->file('ref_img_file')) 
+                ? $request->file('ref_img_file') 
+                : [$request->file('ref_img_file')];
+            
+            foreach ($refImgFiles as $index => $refImgFile) {
+                // 验证图片分辨率和比例
+                $refImgInfo = @getimagesize($refImgFile->getPathname());    
+                
+                if (!$refImgInfo) {
+                    Utils::throwError('1003:图片' . ($index + 1) . '格式无效');
+                }
+                
+                $refImg = [
+                    'width' => $refImgInfo[0],
+                    'height' => $refImgInfo[1],
+                    'size' => $refImgFile->getSize(),
+                ];
+                
+                // 验证图片规格
+                $this->validateImageSpecs($refImg, $index + 1);
+                
+                // 上传图片到服务器并获取URL
+                $filename = randStr(10) . '.' . $refImgFile->getClientOriginalExtension();
+                $uploaded_url = uploadStreamByTos('image', file_get_contents($refImgFile->getPathname()), $filename);
+                if (!$uploaded_url) {
+                    Utils::throwError('1003:图片' . ($index + 1) . '保存失败');
+                }
+                $refImgUrls[] = $uploaded_url;
+            }
+
+        } else {
+            // 使用URL方式,支持逗号分隔的字符串或数组
+            $urls = is_array($data['ref_img_url']) 
+                ? $data['ref_img_url'] 
+                : array_map('trim', explode(',', $data['ref_img_url']));
+            
+            foreach ($urls as $index => $url) {
+                if (empty($url)) {
+                    continue;
+                }
+                
+                // 验证URL格式
+                if (!filter_var($url, FILTER_VALIDATE_URL)) {
+                    Utils::throwError('1003:图片URL' . ($index + 1) . '格式无效');
+                }
+                
+                // 验证远程图片
+                $refImg = Utils::getRemoteImageInfo($url, true);
+                if (!$refImg) {
+                    Utils::throwError('1003:图片' . ($index + 1) . '获取失败');
+                }
+                
+                // 验证图片规格
+                $this->validateImageSpecs($refImg, $index + 1);
+                
+                $refImgUrls[] = $url;
+            }
+        }
+        
+        if (empty($refImgUrls)) {
+            Utils::throwError('1003:至少需要一张参考图片');
+        }
+        
+        // 将处理后的URL存入data(支持多个)
+        $data['ref_img_urls'] = $refImgUrls;
+        $data['ref_img_url'] = $refImgUrls[0]; // 保持向后兼容
+
         try {
             $task = $this->aiImageGenerationService->createImageGenerationTask($data);
 
@@ -67,6 +171,35 @@ class ImageGenerationController extends BaseController
     }
 
     /**
+     * 验证图片规格
+     * 
+     * @param array $refImg
+     * @param int $index
+     * @return void
+     */
+    private function validateImageSpecs(array $refImg, int $index): void
+    {
+        // 判断图片是否满足以下条件:
+        // 1.图片文件最大4.7MB
+        // 2.图片分辨率最大: 4096*4096, 最短边不低于320
+        // 3.图片长边与短边比例在3以内
+        if ($refImg['size'] > 4.7 * 1024 * 1024) {
+            Utils::throwError('1003:图片' . $index . '大小不能超过4.7MB');
+        }
+        
+        $minDimension = min($refImg['width'], $refImg['height']);
+        $maxDimension = max($refImg['width'], $refImg['height']);
+        
+        if ($maxDimension > 4096 || $minDimension < 320) {
+            Utils::throwError('1003:图片' . $index . '分辨率不能超过4096*4096,且最短边不能低于320');
+        }
+        
+        if ($maxDimension / $minDimension > 3) {
+            Utils::throwError('1003:图片' . $index . '长边与短边比例不能超过3');
+        }
+    }
+
+    /**
      * 查询任务状态
      *
      * @param string $taskId
@@ -110,4 +243,4 @@ class ImageGenerationController extends BaseController
         $result = $this->aiImageGenerationService->getTaskList($data);
         return $this->success($result, [new AIGenerationTransformer(), 'newBuildTaskList']);
     }
-}
+}

+ 3 - 3
app/Http/Controllers/AIGeneration/VideoGenerationController.php

@@ -63,7 +63,7 @@ class VideoGenerationController extends BaseController
         } else {
             // 上传文件方式
             $validationRules = array_merge($baseRules, [
-                'first_frame_file' => 'required|image|mimes:jpeg,png|max:4800', // 4.7MB = 4915KB
+                'first_frame_file' => 'required|image|mimes:jpeg,png|max:4800',
                 'tail_frame_file' => 'required|image|mimes:jpeg,png|max:4800',
             ]);
             
@@ -113,7 +113,7 @@ class VideoGenerationController extends BaseController
                 'size' => $tailFrameFile->getSize(),
             ];
             
-            // 上传图片到服务器并获取URL
+            // 本地保存
             $firstFramePath = $firstFrameFile->store('video_frames', 'public');
             $tailFramePath = $tailFrameFile->store('video_frames', 'public');
 
@@ -121,7 +121,7 @@ class VideoGenerationController extends BaseController
             $firstFramePath = storage_path('app/public/' . $firstFramePath);
             $tailFramePath = storage_path('app/public/' . $tailFramePath);
             
-            // 图片上传的方式获取图片的图片文件base64编码
+            // 图片上传的方式获取图片的图片文件base64编码
             $data['first_frame_base64'] = 'data:image/' . $firstFrameExtension . ';base64,' . base64_encode(file_get_contents($firstFramePath));
             $data['tail_frame_base64'] = 'data:image/' . $tailFrameExtension . ';base64,' . base64_encode(file_get_contents($tailFramePath));
 

+ 96 - 2
app/Libs/Helpers.php

@@ -12,6 +12,7 @@ use Tos\TosClient;
 use Tos\Exception\TosClientException;
 use Tos\Exception\TosServerException;
 use Tos\Model\PutObjectInput;
+// use Imagick;
 
 /**
  * 截取字符串
@@ -278,8 +279,10 @@ function uploadFileByTos($prefix, $file, $filename='')
         $client = new TosClient([
             'region'    => env('VOLC_REGION'),
             'endpoint'  => env('VOLC_END_POINT'),
-            'ak'        => env('VOLC_AK'),
-            'sk'        => env('VOLC_SK'),
+            // 'ak'        => env('VOLC_AK'),
+            // 'sk'        => env('VOLC_SK'),
+            'ak'        => 'AKLTNDRjMzdiODkwMThhNDdhZThhOTI3MDg5MDkzYzExNTg',
+            'sk'        => 'TXpOak4yWmxZemxrTVRVMU5EZzVNMkprT0dJMU9HSTBaR1kzWW1NMk5qaw==',
         ]);
         
         $stream = fopen($file->getRealPath(), 'r');
@@ -2110,6 +2113,7 @@ function compressRemoteImageUrlToSize(string $url, int $maxBytes = 3 * 1024 * 10
     // 2) 识别图片类型
     $imgInfo = @getimagesizefromstring($data);
     $mime    = $imgInfo['mime'] ?? '';
+    if ($mime == 'image/jpeg') return null; // JPEG 不做有损压缩
     // 3) 载入图像对象
     $srcImg = @imagecreatefromstring($data);
     if (!$srcImg) {
@@ -2248,3 +2252,93 @@ function safeDestroyImage(&$img)
     }
     $img = null;
 }
+
+// /**
+//  * 使用 Imagick 将远程图片压缩至不超过 maxBytes(默认3MB)内
+//  * 目标:在尽量保留原始格式和质量的前提下进行压缩,避免内存暴涨
+//  * 依赖:Imagick 扩展与 ImageMagick 库,下载使用 Guzzle 获取图片 blob
+//  *
+//  * @param string $url
+//  * @param int    $maxBytes 最大字节数,默认 3*1024*1024
+//  * @return string|null 经过压缩后的图片 blob,失败返回 null
+//  */
+// function compressRemoteImageUrlToSizeImagick(string $url, int $maxBytes = 3 * 1024 * 1024): ?string
+// {
+//     // 兼容性检查
+//     if (!extension_loaded('imagick') || !class_exists('Imagick')) {
+//         return null;
+//     }
+
+//     try {
+//         $client = new Client(['timeout' => 30]);
+//         // 使用流式下载,将数据写入临时文件,避免一次性加载到内存
+//         $response = $client->get($url, ['stream' => true]);
+//         if ($response->getStatusCode() !== 200) {
+//             return null;
+//         }
+
+//         $body = $response->getBody();
+//         $tmp  = tmpfile();
+//         if ($tmp === false) {
+//             return null;
+//         }
+//         stream_copy_to_stream($body, $tmp);
+//         rewind($tmp);
+
+//         $imagick = new Imagick();
+//         $imagick->readImageFile($tmp);
+//         $origW = $imagick->getImageWidth();
+//         $origH = $imagick->getImageHeight();
+//         $format = strtolower($imagick->getImageFormat());
+
+//         // 尝试序列:尺寸缩放 + 质量调节
+//         $scales   = [1.0, 0.9, 0.8, 0.6, 0.4, 0.25];
+//         $qualities = [95, 90, 85, 75, 60, 40, 30, 20];
+//         $bestBlob = null;
+
+//         foreach ($scales as $scale) {
+//             foreach ($qualities as $q) {
+//                 $clone = clone $imagick;
+//                 if ($scale < 1.0) {
+//                     $w = (int)round($origW * $scale);
+//                     $h = (int)round($origH * $scale);
+//                     if ($w < 1 || $h < 1) {
+//                         $clone->destroy();
+//                         continue;
+//                     }
+//                     $clone->resizeImage($w, $h, Imagick::FILTER_LANCZOS, 1);
+//                 }
+//                 $clone->setImageFormat($format);
+//                 $clone->setImageCompressionQuality($q);
+//                 $blob = $clone->getImageBlob();
+//                 $clone->destroy();
+//                 if ($blob !== false && strlen($blob) <= $maxBytes) {
+//                     $bestBlob = $blob;
+//                     break 2;
+//                 }
+//             }
+//         }
+
+//         if ($bestBlob !== null) {
+//             $imagick->destroy();
+//             fclose($tmp);
+//             return $bestBlob;
+//         }
+
+//         // 回退:尝试强制输出为当前格式的一个中等质量版本
+//         $fallback = clone $imagick;
+//         $fallback->setImageFormat($format);
+//         $fallback->setImageCompressionQuality(75);
+//         $blob = $fallback->getImageBlob();
+//         $fallback->destroy();
+//         $imagick->destroy();
+//         fclose($tmp);
+//         if ($blob !== false && strlen($blob) <= $maxBytes) {
+//             return $blob;
+//         }
+//     } catch (\Exception $e) {
+//         return null;
+//     }
+
+//     return null;
+// }

+ 13 - 7
app/Services/AIGeneration/AIImageGenerationService.php

@@ -41,7 +41,7 @@ class AIImageGenerationService
             'height' => $params['height'] ?? 2048,
             'image_num' => $params['image_num'] ?? 1,
             'scale' => $params['scale'] ?? 50,
-            'ref_img_url' => $params['ref_img_url'] ?? null,
+            'ref_img_url' => $params['ref_img_urls'] ?? null,
             'mask_img_url' => $params['mask_img_url'] ?? null,
             'extra_params' => $params['extra_params'] ?? null,
             'status' => MpGeneratePicTask::STATUS_PENDING,
@@ -89,12 +89,12 @@ class AIImageGenerationService
             
             // 参考图片
             if ($task->ref_img_url) {
-                $apiParams['image_urls'] = [$task->ref_img_url];
+                $apiParams['image_urls'] = $task->ref_img_url;
             }
                         
-            if ($task->mask_img_url) {
-                $apiParams['image_urls'][] = $task->mask_img_url;
-            }
+            // if ($task->mask_img_url) {
+            //     $apiParams['image_urls'] = $task->mask_img_url;
+            // }
             
             // 尺寸参数
             $area = $task->width * $task->height; //计算面积
@@ -235,8 +235,14 @@ class AIImageGenerationService
                     $pic_ext = getExtFromContent($url);
                     $pic_name = $pic_name . $pic_ext;
                     
-                    // 将图片另存到tos
-                    $url = uploadStreamByTos('image', file_get_contents($url), $pic_name);
+                    if ($pic_ext === '.png') {
+                        $comporessed_data = compressRemoteImageUrlToSize($url);
+                        $url = uploadStreamByTos('image', $comporessed_data, $pic_name);
+                    }else {
+                        // 将图片另存到tos
+                        $url = uploadStreamByTos('image', file_get_contents($url), $pic_name);
+                    }
+                    
                     $result_urls[] = $url;
                 }
                 $returnData['result_url'] = $result_urls;