paths.py
5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"""应用数据路径 + 图片格式校验工具。
跨平台数据目录探测(macOS .app 包外存储 / Windows APPDATA / 开发环境同目录),
PNG/JPEG 格式回正(Pillow 重写防止伪装 MIME)。
"""
import io
import logging
import os
import platform
import shutil
import sys
from pathlib import Path
def _migrate_data_from_app_bundle(target_path: Path):
"""将 .app 内部的旧数据迁移到外部目录(仅 macOS 打包环境)"""
if not (getattr(sys, 'frozen', False) and platform.system() == "Darwin"):
return
old_path = Path(sys.executable).parent / "images"
if not old_path.exists() or old_path == target_path:
return
old_files = list(old_path.rglob("*"))
if not old_files:
return
try:
target_path.mkdir(parents=True, exist_ok=True)
migrated = 0
for src_file in old_path.rglob("*"):
if src_file.is_file():
rel_path = src_file.relative_to(old_path)
dst_file = target_path / rel_path
if not dst_file.exists():
dst_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(str(src_file), str(dst_file))
migrated += 1
print(f"已从 .app 内部迁移 {migrated} 个文件到: {target_path}")
except Exception as e:
print(f"数据迁移失败(不影响使用): {e}")
def get_app_data_path() -> Path:
"""获取应用数据存储路径 - 智能选择"""
def get_candidate_paths():
system = platform.system()
candidates = []
if getattr(sys, 'frozen', False) and system == "Darwin":
candidates.append(Path.home() / "Library/Application Support/ZB100ImageGenerator/images")
elif getattr(sys, 'frozen', False):
candidates.append(Path(sys.executable).parent / "images")
else:
# 开发环境:保持和老路径一致 —— 项目根目录下的 images/
# __file__ 在 core/,需要往上一级
candidates.append(Path(__file__).resolve().parent.parent / "images")
if system == "Darwin":
candidates.append(Path.home() / "Library/Application Support/ZB100ImageGenerator/images")
candidates.append(Path.home() / "Documents/ZB100ImageGenerator/images")
elif system == "Windows":
candidates.append(Path(os.environ.get("APPDATA", "")) / "ZB100ImageGenerator/images")
candidates.append(Path.home() / "Documents/ZB100ImageGenerator/images")
else:
candidates.append(Path.home() / ".config/zb100imagegenerator/images")
candidates.append(Path.home() / "Documents/ZB100ImageGenerator/images")
return candidates
def test_path_write_access(path: Path) -> bool:
try:
path.mkdir(parents=True, exist_ok=True)
test_file = path / ".write_test"
test_file.write_text("test")
test_file.unlink()
return True
except (PermissionError, OSError) as e:
print(f"路径 {path} 无写入权限: {e}")
return False
except Exception as e:
print(f"路径 {path} 测试失败: {e}")
return False
candidates = get_candidate_paths()
for path in candidates:
if test_path_write_access(path):
_migrate_data_from_app_bundle(path)
print(f"使用图片存储路径: {path}")
return path
fallback_path = get_candidate_paths()[0]
try:
fallback_path.mkdir(parents=True, exist_ok=True)
print(f"使用备选路径: {fallback_path}")
return fallback_path
except Exception as e:
print(f"警告: 无法创建存储路径,将在当前目录操作: {e}")
return Path.cwd() / "images"
def save_png_with_validation(file_path: str, image_bytes: bytes) -> bool:
"""使用 Pillow 验证并重写 PNG/JPEG,确保 MIME 与扩展名一致。
返回 True 表示 Pillow 处理成功;False 表示 Pillow 不可用或处理失败,
调用方应回退到原始 write_bytes。
"""
try:
from PIL import Image
with Image.open(io.BytesIO(image_bytes)) as img:
file_format = img.format
if file_format == 'JPEG':
logger = logging.getLogger(__name__)
logger.info(f"检测到伪装PNG的JPEG文件,实际格式: {file_format}")
save_format = 'PNG' if file_path.lower().endswith('.png') else 'JPEG'
if file_format and file_format != save_format:
logger = logging.getLogger(__name__)
logger.info(f"执行格式转换: {file_format} -> {save_format}")
if save_format == 'PNG':
if img.mode not in ['RGBA', 'RGB', 'L']:
if img.mode == 'P':
img = img.convert('RGBA')
elif img.mode == 'LA':
img = img.convert('RGBA')
else:
img = img.convert('RGBA')
elif save_format == 'JPEG':
if img.mode in ['RGBA', 'P']:
img = img.convert('RGB')
elif img.mode == 'L':
img = img.convert('RGB')
img.save(file_path, save_format, optimize=True)
logger = logging.getLogger(__name__)
logger.info(f"图片格式验证成功: {file_path}, 保存格式: {save_format}")
return True
except ImportError:
logger = logging.getLogger(__name__)
logger.warning("Pillow库不可用,使用原始保存方法")
return False
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning(f"Pillow处理失败,使用原始保存方法: {e}")
return False