diff --git a/apps/jfile/files/25_5uvvahf6kj_d9c433c5_1.png b/apps/jfile/files/25_5uvvahf6kj_d9c433c5_1.png new file mode 100644 index 0000000..9d6fe0e Binary files /dev/null and b/apps/jfile/files/25_5uvvahf6kj_d9c433c5_1.png differ diff --git a/apps/jfile/files/split_2acec28387_1.png b/apps/jfile/files/split_2acec28387_1.png new file mode 100644 index 0000000..6256135 Binary files /dev/null and b/apps/jfile/files/split_2acec28387_1.png differ diff --git a/apps/jfile/files/split_2acec28387_2.png b/apps/jfile/files/split_2acec28387_2.png new file mode 100644 index 0000000..b1c2363 Binary files /dev/null and b/apps/jfile/files/split_2acec28387_2.png differ diff --git a/apps/jfile/files/split_2acec28387_3.png b/apps/jfile/files/split_2acec28387_3.png new file mode 100644 index 0000000..40a3a1f Binary files /dev/null and b/apps/jfile/files/split_2acec28387_3.png differ diff --git a/apps/jfile/files/split_2acec28387_4.png b/apps/jfile/files/split_2acec28387_4.png new file mode 100644 index 0000000..1b82fcb Binary files /dev/null and b/apps/jfile/files/split_2acec28387_4.png differ diff --git a/apps/jfile/files/split_c4191175e4_1.png b/apps/jfile/files/split_c4191175e4_1.png new file mode 100644 index 0000000..6bfabf3 Binary files /dev/null and b/apps/jfile/files/split_c4191175e4_1.png differ diff --git a/apps/jfile/files/split_c4191175e4_2.png b/apps/jfile/files/split_c4191175e4_2.png new file mode 100644 index 0000000..525bcf5 Binary files /dev/null and b/apps/jfile/files/split_c4191175e4_2.png differ diff --git a/apps/jfile/files/split_c4191175e4_3.png b/apps/jfile/files/split_c4191175e4_3.png new file mode 100644 index 0000000..7421285 Binary files /dev/null and b/apps/jfile/files/split_c4191175e4_3.png differ diff --git a/apps/jfile/files/split_c4191175e4_4.png b/apps/jfile/files/split_c4191175e4_4.png new file mode 100644 index 0000000..efa8c20 Binary files /dev/null and b/apps/jfile/files/split_c4191175e4_4.png differ diff --git a/apps/jfile/files/upscaled_2fead90ea9.jpg b/apps/jfile/files/upscaled_2fead90ea9.jpg deleted file mode 100644 index 8f920b3..0000000 Binary files a/apps/jfile/files/upscaled_2fead90ea9.jpg and /dev/null differ diff --git a/apps/jfile/files/upscaled_466670e1cb.jpg b/apps/jfile/files/upscaled_466670e1cb.jpg deleted file mode 100644 index 900118e..0000000 Binary files a/apps/jfile/files/upscaled_466670e1cb.jpg and /dev/null differ diff --git a/apps/midjourney/service.py b/apps/midjourney/service.py index fa435e4..091c0dc 100644 --- a/apps/midjourney/service.py +++ b/apps/midjourney/service.py @@ -289,10 +289,17 @@ class MidjourneyService: return image_urls async def split_image(self, image_url): - """将一张大图切割成四张子图,保存到本地并返回URLs""" + """将一张大图切割成四张子图,保存到本地并返回URLs + + Args: + image_url: 原始图像的URL + + Returns: + 包含四个子图像URL的列表,如果处理失败则返回None + """ try: # 下载图像 - response = requests.get(image_url, proxies=self.proxies if self.proxies else None) + response = requests.get(image_url, proxies=self.proxies if self.proxies else None, timeout=30) if response.status_code != 200: logger.error(f"下载图像失败,状态码: {response.status_code}") return None @@ -303,9 +310,18 @@ class MidjourneyService: img = Image.open(io.BytesIO(image_data)) width, height = img.size + # 获取原始图片格式 + original_format = img.format + if not original_format: + # 如果无法获取格式,从URL中提取 + parsed_url = urlparse(image_url) + original_format = os.path.splitext(parsed_url.path)[1][1:].upper() + if not original_format: + original_format = 'PNG' # 默认使用PNG + # 确认图像尺寸约为2048x2048 if width < 1500 or height < 1500: - logger.error(f"图像尺寸不符合预期: {width}x{height}") + logger.error(f"图像尺寸不符合预期: {width}x{height}, 应该接近2048x2048") return None # 计算每个象限的尺寸 @@ -313,29 +329,70 @@ class MidjourneyService: half_height = height // 2 # 分割图像为四个象限 - top_left = img.crop((0, 0, half_width, half_height)) - top_right = img.crop((half_width, 0, width, half_height)) - bottom_left = img.crop((0, half_height, half_width, height)) - bottom_right = img.crop((half_width, half_height, width, height)) + quadrants = [ + img.crop((0, 0, half_width, half_height)), # 左上 + img.crop((half_width, 0, width, half_height)), # 右上 + img.crop((0, half_height, half_width, height)), # 左下 + img.crop((half_width, half_height, width, height)) # 右下 + ] # 生成唯一的图片名称前缀 image_id = uuid.uuid4().hex[:10] + # 确保保存目录存在(使用绝对路径) + save_dir = os.path.abspath(settings.save_dir) + os.makedirs(save_dir, exist_ok=True) + # 保存图片到本地并生成URLs image_urls = [] - for i, quadrant in enumerate([top_left, top_right, bottom_left, bottom_right], 1): - # 生成文件名和保存路径 - filename = f"split_{image_id}_{i}.png" - file_path = os.path.join(settings.save_dir, filename) + for i, quadrant in enumerate(quadrants, 1): + try: + # 使用原始格式保存 + filename = f"split_{image_id}_{i}.{original_format.lower()}" + file_path = os.path.join(save_dir, filename) + + # 保存图片,保持原始格式 + save_params = {"format": original_format} + if original_format in ['PNG', 'JPEG', 'JPG']: + save_params["optimize"] = True + if original_format in ['JPEG', 'JPG']: + save_params["quality"] = 95 + + quadrant.save(file_path, **save_params) + + # 验证文件是否成功保存 + if not os.path.exists(file_path): + raise Exception(f"文件保存失败: {file_path}") + + # 验证文件大小 + file_size = os.path.getsize(file_path) + if file_size == 0: + raise Exception(f"保存的文件大小为0: {file_path}") + + # 构建图片URL + image_url = f"{settings.download_url}/{filename}" + image_urls.append(image_url) + + logger.info(f"成功保存分割图片 {i}/4: {filename} (大小: {file_size} 字节, 格式: {original_format})") + except Exception as e: + logger.error(f"保存分割图片 {i}/4 失败: {str(e)}") + # 如果保存失败,删除已保存的图片 + for url in image_urls: + try: + file_path = os.path.join(save_dir, os.path.basename(url)) + if os.path.exists(file_path): + os.remove(file_path) + except Exception as del_e: + logger.error(f"删除失败的图片文件时出错: {str(del_e)}") + return None - # 保存图片 - quadrant.save(file_path, format="PNG") - - # 构建图片URL - image_url = f"{settings.download_url}/{filename}" - image_urls.append(image_url) + if len(image_urls) != 4: + logger.error(f"分割图片数量不正确: 期望4张,实际{len(image_urls)}张") + return None + logger.info(f"成功完成图片分割,生成4张子图") return image_urls + except Exception as e: logger.error(f"分割图像失败: {str(e)}") traceback.print_exc() diff --git a/apps/midjourney/settings.py b/apps/midjourney/settings.py index 95322da..d407d7f 100644 --- a/apps/midjourney/settings.py +++ b/apps/midjourney/settings.py @@ -17,9 +17,9 @@ class Settings(BaseSettings): upload_url: str = "http://images.jingrow.com:8080/api/v1/image" # 图片保存配置 - save_dir: str = "../jfile/midjourney" + save_dir: str = "../jfile/files" # Japi 静态资源下载URL - download_url: str = "http://api.jingrow.com:9080/midjourney" + download_url: str = "http://api.jingrow.com:9080/files" # Jingrow Jcloud API 配置 jingrow_api_url: str = "https://cloud.jingrow.com" diff --git a/apps/midjourney/utils.py b/apps/midjourney/utils.py index 62bb8ce..fd87fcb 100644 --- a/apps/midjourney/utils.py +++ b/apps/midjourney/utils.py @@ -175,87 +175,6 @@ def is_valid_image_url(url: str) -> bool: except: return False -def validate_image_file(file_path: str) -> bool: - """验证图片文件是否有效 - - Args: - file_path: 图片文件路径 - - Returns: - bool: 文件是否有效 - """ - try: - with Image.open(file_path) as img: - img.verify() - return True - except: - return False - -def get_image_size(image_url: str) -> Optional[Tuple[int, int]]: - """获取图片尺寸 - - Args: - image_url: 图片URL - - Returns: - Optional[Tuple[int, int]]: 图片尺寸(宽,高),如果获取失败则返回None - """ - try: - response = requests.get(image_url, verify=False, timeout=10) - if response.status_code != 200: - return None - - with Image.open(io.BytesIO(response.content)) as img: - return img.size - except: - return None - -def is_valid_image_size(image_url: str, min_size: int = 512) -> bool: - """验证图片尺寸是否满足最小要求 - - Args: - image_url: 图片URL - min_size: 最小尺寸要求 - - Returns: - bool: 图片尺寸是否满足要求 - """ - size = get_image_size(image_url) - if not size: - return False - width, height = size - return width >= min_size and height >= min_size - -def extract_image_urls_from_text(text: str) -> List[str]: - """从文本中提取图片URL - - Args: - text: 包含图片URL的文本 - - Returns: - List[str]: 提取到的图片URL列表 - """ - # 匹配常见的图片URL模式 - url_pattern = r'https?://[^\s<>"]+?\.(?:jpg|jpeg|png|webp|gif)(?:\?[^\s<>"]*)?' - urls = re.findall(url_pattern, text, re.IGNORECASE) - return [url for url in urls if is_valid_image_url(url)] - -def sanitize_filename(filename: str) -> str: - """清理文件名,移除非法字符 - - Args: - filename: 原始文件名 - - Returns: - str: 清理后的文件名 - """ - # 移除非法字符 - filename = re.sub(r'[<>:"/\\|?*]', '', filename) - # 限制长度 - if len(filename) > 255: - name, ext = os.path.splitext(filename) - filename = name[:255-len(ext)] + ext - return filename def get_new_image_url(image_url: str) -> str: """将图片URL转换为新的存储URL