修复输出格式解析bug:.jpg和jpg能正确转换
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
from watermark import watermark_image, increment_count
|
||||
from config import OUTPUT_DIR, SUPPORTED_INPUT_EXTENSIONS, SUPPORTED_OUTPUT_EXTENSIONS, WORK_DIR
|
||||
from watermark import watermark_image_tiled, increment_count, _parse_output_spec
|
||||
from config import SUPPORTED_INPUT_EXTENSIONS, SUPPORTED_OUTPUT_EXTENSIONS, WORK_DIR
|
||||
|
||||
|
||||
def print_banner():
|
||||
@@ -12,10 +12,12 @@ def print_banner():
|
||||
print(" 输出: jpg / png / bmp / gif / tiff / webp")
|
||||
print("=" * 50)
|
||||
print("命令:")
|
||||
print(" <图片路径> \"水印内容\" 加水印,保持原格式输出")
|
||||
print(" <图片路径> \"水印内容\" 加水印,输出到原目录")
|
||||
print(" <图片路径>>.png \"内容\" 加水印并转换格式")
|
||||
print(" /exit 或 exit 退出程序")
|
||||
print(" /get 或 get 显示已处理图片统计")
|
||||
print(" <图片路径>>./output.png 输出到指定目录")
|
||||
print(" <图片路径>>../output.png 输出到上级目录")
|
||||
print(" /exit 或 exit 退出程序")
|
||||
print(" /get 或 get 显示已处理图片统计")
|
||||
print()
|
||||
|
||||
|
||||
@@ -31,35 +33,53 @@ def print_stats():
|
||||
|
||||
|
||||
def process_single(image_arg, text):
|
||||
"""处理单张图片:可选格式转换,添加水印,计数+1。"""
|
||||
# 去掉首尾引号(兼容命令行传入带引号的路径)
|
||||
"""处理单张图片:可选格式转换/输出路径,添加平铺水印,计数+1。"""
|
||||
# 去掉首尾引号
|
||||
image_arg = image_arg.strip().strip('"').strip("'")
|
||||
text = text.strip().strip('"').strip("'")
|
||||
|
||||
if ">" not in image_arg:
|
||||
output_ext = None
|
||||
else:
|
||||
output_spec = None
|
||||
if ">" in image_arg:
|
||||
parts = image_arg.rsplit(">", 1)
|
||||
image_arg, out_ext = parts[0].strip(), parts[1].strip().lstrip(".")
|
||||
if out_ext.lower() not in SUPPORTED_INPUT_EXTENSIONS:
|
||||
print(f"不支持的输出格式: .{out_ext},支持的格式: {', '.join(sorted(SUPPORTED_OUTPUT_EXTENSIONS))}")
|
||||
return
|
||||
output_ext = f".{out_ext.lower()}"
|
||||
image_arg = parts[0].strip()
|
||||
output_spec = parts[1].strip()
|
||||
|
||||
# 验证输入路径
|
||||
input_path = image_arg
|
||||
if not os.path.isfile(input_path):
|
||||
print(f"找不到输入图片: {input_path}")
|
||||
print(f"❌ 找不到输入图片: {input_path}")
|
||||
return
|
||||
|
||||
_, ext = os.path.splitext(input_path)
|
||||
if ext.lower() not in SUPPORTED_INPUT_EXTENSIONS:
|
||||
print(f"不支持的输入格式: {ext},支持的格式: {', '.join(sorted(SUPPORTED_INPUT_EXTENSIONS))}")
|
||||
print(f"❌ 不支持的输入格式: {ext},支持的格式: {', '.join(sorted(SUPPORTED_INPUT_EXTENSIONS))}")
|
||||
return
|
||||
|
||||
output_path = watermark_image(input_path, text, output_ext=output_ext)
|
||||
total = increment_count()
|
||||
print(f"✅ 已保存: {output_path}")
|
||||
print(f"📊 累计处理图片: {total} 张")
|
||||
# 处理输出规格
|
||||
output_path = None
|
||||
output_ext = None
|
||||
if output_spec:
|
||||
try:
|
||||
output_path, output_ext = _parse_output_spec(output_spec, input_path)
|
||||
except ValueError as e:
|
||||
print(f"❌ {e}")
|
||||
return
|
||||
else:
|
||||
# 无输出规格,保持原格式,输出到原目录
|
||||
_, output_ext = os.path.splitext(input_path)
|
||||
input_dir = os.path.dirname(os.path.abspath(input_path))
|
||||
base_name = os.path.basename(input_path)
|
||||
name_no_ext = os.path.splitext(base_name)[0]
|
||||
output_path = os.path.join(input_dir, f"{name_no_ext}_watermarked{output_ext.lower()}")
|
||||
|
||||
# 添加平铺水印
|
||||
try:
|
||||
final_path = watermark_image_tiled(input_path, text, output_path=output_path, output_ext=output_ext)
|
||||
total = increment_count()
|
||||
print(f"✅ 已保存: {final_path}")
|
||||
print(f"📊 累计处理图片: {total} 张")
|
||||
except Exception as e:
|
||||
print(f"❌ 处理失败: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
@@ -88,26 +108,42 @@ def main():
|
||||
elif cmd in ("/help", "help"):
|
||||
print_banner()
|
||||
elif ">" in cmd:
|
||||
# 格式转换模式: "图片路径>.png 水印内容" 或 "图片路径>PNG 水印内容"
|
||||
# 格式转换模式: "图片路径>.png 水印内容" 或 "图片路径>./output.png 水印内容"
|
||||
parts = cmd.split(">", 1)
|
||||
image_part = parts[0].strip()
|
||||
rest = parts[1].strip()
|
||||
if not rest:
|
||||
print("缺少水印内容,请重试。输入 /help 查看帮助。")
|
||||
print("❌ 缺少输出规格或水印内容,请重试。")
|
||||
continue
|
||||
# rest 中第一个空格前是格式,后面是水印文本
|
||||
|
||||
# 分析 rest 部分:可能是 "格式 文本" 或 "路径 文本"
|
||||
tokens = rest.split(None, 1)
|
||||
if len(tokens) < 2:
|
||||
print("格式参数后缺少水印内容,请重试。输入 /help 查看帮助。")
|
||||
if len(tokens) == 2:
|
||||
output_spec, text = tokens
|
||||
full_arg = f"{image_part}>{output_spec}"
|
||||
elif len(tokens) == 1:
|
||||
# 只有一个 token,判断是否是路径(包含 . 或 / 或 \)
|
||||
candidate = tokens[0]
|
||||
if "." in candidate or "/" in candidate or "\\" in candidate:
|
||||
# 可能是路径,但缺少水印内容
|
||||
print("❌ 缺少水印内容,请重试。")
|
||||
continue
|
||||
else:
|
||||
# 是格式名
|
||||
output_spec = candidate
|
||||
# 继续读取后续内容作为水印文本(如果有)
|
||||
print("❌ 缺少水印内容,请重试。")
|
||||
continue
|
||||
else:
|
||||
print("❌ 格式错误,请重试。")
|
||||
continue
|
||||
final_format, text = tokens
|
||||
full_arg = f"{image_part}>{final_format}"
|
||||
|
||||
process_single(full_arg, text)
|
||||
else:
|
||||
# 普通模式: "图片路径 水印内容"
|
||||
parts = cmd.split(None, 1)
|
||||
if len(parts) < 2:
|
||||
print("格式错误,需要提供图片路径和水印内容。输入 /help 查看帮助。")
|
||||
print("❌ 格式错误,需要提供图片路径和水印内容。")
|
||||
continue
|
||||
image_arg, text = parts
|
||||
process_single(image_arg, text)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
+106
-32
@@ -1,9 +1,9 @@
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from config import OUTPUT_DIR, SUPPORTED_INPUT_EXTENSIONS, SUPPORTED_OUTPUT_EXTENSIONS, STATS_FILE
|
||||
from config import SUPPORTED_INPUT_EXTENSIONS, SUPPORTED_OUTPUT_EXTENSIONS, STATS_FILE
|
||||
|
||||
|
||||
def load_font(size=48):
|
||||
def load_font(size=16):
|
||||
"""根据系统加载合适的字体文件。"""
|
||||
candidates = [
|
||||
r"C:\Windows\Fonts\msyh.ttc",
|
||||
@@ -17,69 +17,143 @@ def load_font(size=48):
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def _normalize_path(path):
|
||||
"""规范化路径,统一使用正斜杠,解析相对路径。"""
|
||||
# 统一将反斜杠转为正斜杠
|
||||
path = path.replace("\\", "/")
|
||||
# 规范化相对路径
|
||||
path = os.path.normpath(path)
|
||||
return path
|
||||
|
||||
|
||||
def _strip_quotes(s):
|
||||
"""去掉首尾的引号(\"、')"""
|
||||
"""去掉首尾的引号。"""
|
||||
s = s.strip()
|
||||
if len(s) >= 2 and s[0] == s[-1] and s[0] in ('"', "'"):
|
||||
return s[1:-1]
|
||||
return s
|
||||
|
||||
|
||||
def watermark_image(input_path, text, output_path=None, output_ext=None):
|
||||
def _parse_output_spec(output_spec, input_path):
|
||||
"""
|
||||
给图片添加水印并保存。
|
||||
解析输出规格,返回 (output_path, output_ext)。
|
||||
|
||||
支持格式:
|
||||
- ".png" / "png" -> 使用原目录,转换格式
|
||||
- "./output.png" -> 输出到当前目录
|
||||
- "../output.png" -> 输出到上级目录
|
||||
- "/absolute/path.png" -> 绝对路径
|
||||
- "output.png" -> 输出到原目录(重命名)
|
||||
"""
|
||||
if not output_spec:
|
||||
# 无格式指定,保持原格式和原目录
|
||||
input_dir = os.path.dirname(os.path.abspath(input_path))
|
||||
base_name = os.path.basename(input_path)
|
||||
name_no_ext = os.path.splitext(base_name)[0]
|
||||
_, ext = os.path.splitext(input_path)
|
||||
return os.path.join(input_dir, f"{name_no_ext}_watermarked{ext.lower()}"), ext.lower()
|
||||
|
||||
output_spec = _strip_quotes(output_spec.strip())
|
||||
output_spec = _normalize_path(output_spec)
|
||||
|
||||
# 分离目录和文件名
|
||||
dir_part = os.path.dirname(output_spec)
|
||||
file_part = os.path.basename(output_spec)
|
||||
|
||||
# 分离文件名和扩展名
|
||||
name_no_ext, ext = os.path.splitext(file_part)
|
||||
|
||||
# 判断是否是纯格式(如 .jpg、jpg)vs 有文件名(如 result.jpg)
|
||||
is_just_format = False
|
||||
if not name_no_ext:
|
||||
# 没有文件名部分,只有扩展名
|
||||
is_just_format = True
|
||||
elif name_no_ext == file_part and len(name_no_ext) <= 5 and name_no_ext.isalpha():
|
||||
# 没有点,整个是字母文件名(如 jpg)
|
||||
is_just_format = True
|
||||
elif file_part.startswith('.') and len(file_part) <= 5 and file_part[1:].isalpha():
|
||||
# 以点开头且其余为字母(如 .jpg)
|
||||
is_just_format = True
|
||||
|
||||
# 如果没有扩展名,使用原图片的扩展名
|
||||
if not ext:
|
||||
_, orig_ext = os.path.splitext(input_path)
|
||||
ext = orig_ext.lower() if orig_ext else ".png"
|
||||
|
||||
# 如果只有文件名没有扩展名,给它加上正确的扩展名
|
||||
if is_just_format:
|
||||
ext = '.' + name_no_ext.lstrip('.') if name_no_ext else ext
|
||||
name_no_ext = ''
|
||||
else:
|
||||
ext = ext.lower()
|
||||
|
||||
if ext not in SUPPORTED_OUTPUT_EXTENSIONS:
|
||||
raise ValueError(f"不支持的输出格式: {ext},支持的格式: {', '.join(sorted(SUPPORTED_OUTPUT_EXTENSIONS))}")
|
||||
|
||||
if is_just_format:
|
||||
# 纯格式转换,使用原目录和原文件名加后缀
|
||||
input_dir = os.path.dirname(os.path.abspath(input_path))
|
||||
base_name = os.path.basename(input_path)
|
||||
name_no_ext = os.path.splitext(base_name)[0]
|
||||
output_path = os.path.join(input_dir, f"{name_no_ext}_watermarked{ext}")
|
||||
elif dir_part:
|
||||
# 有路径,使用指定路径
|
||||
output_path = os.path.join(dir_part, file_part)
|
||||
else:
|
||||
# 只有文件名,没有路径,使用原目录
|
||||
input_dir = os.path.dirname(os.path.abspath(input_path))
|
||||
output_path = os.path.join(input_dir, file_part)
|
||||
|
||||
return _normalize_path(output_path), ext
|
||||
|
||||
|
||||
def watermark_image_tiled(input_path, text, output_path=None, output_ext=None):
|
||||
"""
|
||||
给图片添加平铺水印(小字+全屏,类似身份证水印效果)。
|
||||
|
||||
Args:
|
||||
input_path: 输入图片路径
|
||||
text: 水印文字内容
|
||||
output_path: 输出路径(不含扩展名),默认在 output/ 目录
|
||||
output_ext: 指定输出格式扩展名,如 ".png"、".jpg";为 None 则保持原格式
|
||||
output_path: 输出路径,默认在同目录生成 _watermarked 后缀文件
|
||||
output_ext: 指定输出格式(如 ".png"),None 则保持原格式
|
||||
Returns:
|
||||
最终保存的绝对路径
|
||||
"""
|
||||
img = Image.open(input_path).convert("RGBA")
|
||||
|
||||
# 水印图层(右下角)
|
||||
# 创建平铺水印图层
|
||||
overlay = Image.new("RGBA", img.size, (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(overlay)
|
||||
|
||||
font = load_font(48)
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
# 小字字体
|
||||
font = load_font(16)
|
||||
|
||||
x = img.width - tw - 20
|
||||
y = img.height - th - 20
|
||||
# 计算水印间距(根据图片尺寸自适应)
|
||||
spacing_x = max(80, img.width // 4)
|
||||
spacing_y = max(60, img.height // 3)
|
||||
|
||||
# 半透明圆角背景
|
||||
draw.rounded_rectangle(
|
||||
[x - 8, y - 4, x + tw + 8, y + th + 4],
|
||||
radius=6,
|
||||
fill=(0, 0, 0, 140),
|
||||
)
|
||||
# 白色文字
|
||||
draw.text((x, y), text, fill=(255, 255, 255, 255), font=font)
|
||||
# 绘制平铺水印
|
||||
for y in range(-20, img.height + 20, spacing_y):
|
||||
for x in range(-20, img.width + 20, spacing_x):
|
||||
# 半透明文字(白色,透明度 60/255)
|
||||
draw.text((x, y), text, fill=(255, 255, 255, 60), font=font)
|
||||
|
||||
# 合成并转 RGB
|
||||
# 合成水印
|
||||
result = Image.alpha_composite(img, overlay)
|
||||
result = result.convert("RGB")
|
||||
|
||||
# 确定输出格式
|
||||
# 确定输出格式和路径
|
||||
if output_ext is None:
|
||||
_, ext = os.path.splitext(input_path)
|
||||
output_ext = ext.lower()
|
||||
_, output_ext = os.path.splitext(input_path)
|
||||
output_ext = output_ext.lower()
|
||||
else:
|
||||
output_ext = output_ext.lower()
|
||||
if output_ext not in SUPPORTED_OUTPUT_EXTENSIONS:
|
||||
raise ValueError(
|
||||
f"不支持的输出格式: {output_ext},支持的格式: {', '.join(sorted(SUPPORTED_OUTPUT_EXTENSIONS))}"
|
||||
)
|
||||
|
||||
# 确定输出路径
|
||||
if output_path is None:
|
||||
input_dir = os.path.dirname(os.path.abspath(input_path))
|
||||
base_name = os.path.basename(input_path)
|
||||
name_no_ext = os.path.splitext(base_name)[0]
|
||||
out_name = f"{name_no_ext}_watermarked{output_ext}"
|
||||
output_path = os.path.join(OUTPUT_DIR, out_name)
|
||||
output_path = os.path.join(input_dir, f"{name_no_ext}_watermarked{output_ext}")
|
||||
elif not output_path.endswith(output_ext):
|
||||
output_path = output_path + output_ext
|
||||
|
||||
|
||||
Reference in New Issue
Block a user