初始化图像水印CLI工具
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
output/
|
||||
*.db
|
||||
processed_count
|
||||
.env
|
||||
.venv/
|
||||
venv/
|
||||
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
|
||||
WORK_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
OUTPUT_DIR = os.path.join(WORK_DIR, "output")
|
||||
STATS_FILE = os.path.join(WORK_DIR, ".processed_count")
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
SUPPORTED_INPUT_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".webp"}
|
||||
SUPPORTED_OUTPUT_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".webp"}
|
||||
@@ -0,0 +1,116 @@
|
||||
import os
|
||||
import sys
|
||||
from watermark import watermark_image, increment_count, load_font
|
||||
from config import OUTPUT_DIR, SUPPORTED_INPUT_EXTENSIONS, WORK_DIR
|
||||
|
||||
|
||||
def print_help():
|
||||
help_text = """
|
||||
用法:
|
||||
"图片路径" "水印内容" 加水印,保持原格式输出
|
||||
"图片路径>输出格式" "水印内容" 加水印并转换格式(格式可选:jpg/png/bmp/gif/tiff/webp)
|
||||
/exit 退出程序
|
||||
/get 查询已处理图片总数
|
||||
|
||||
示例:
|
||||
photo.jpg "版权所有"
|
||||
photo.jpg>PNG "2026"
|
||||
"""
|
||||
print(help_text.strip())
|
||||
|
||||
|
||||
def print_stats():
|
||||
count = 0
|
||||
if os.path.exists(os.path.join(WORK_DIR, ".processed_count")):
|
||||
with open(os.path.join(WORK_DIR, ".processed_count"), "r", encoding="utf-8") as f:
|
||||
try:
|
||||
count = int(f.read().strip())
|
||||
except ValueError:
|
||||
pass
|
||||
print(f"当前已处理图片数量: {count}")
|
||||
|
||||
|
||||
def process_single(image_arg, text):
|
||||
if ">" not in image_arg:
|
||||
# 无格式转换,保持原格式
|
||||
output_ext = None
|
||||
else:
|
||||
parts = image_arg.rsplit(">", 1)
|
||||
image_arg, out_ext = parts
|
||||
out_ext = out_ext.strip().lstrip(".")
|
||||
if out_ext.lower() not in SUPPORTED_INPUT_EXTENSIONS:
|
||||
print(f"不支持的输出格式: .{out_ext},支持的格式: {', '.join(sorted(SUPPORTED_INPUT_EXTENSIONS))}")
|
||||
return
|
||||
output_ext = f".{out_ext.lower()}"
|
||||
|
||||
input_path = image_arg.strip()
|
||||
if not os.path.isfile(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))}")
|
||||
return
|
||||
|
||||
output_path = watermark_image(input_path, text, output_ext=output_ext)
|
||||
total = increment_count()
|
||||
print(f"✅ 已保存: {output_path}")
|
||||
print(f"📊 累计处理图片: {total} 张")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) >= 3:
|
||||
# 非交互模式:直接处理一张图片
|
||||
process_single(sys.argv[1], sys.argv[2])
|
||||
return
|
||||
|
||||
print("========================================")
|
||||
print(" 图像水印工具 v1.0")
|
||||
print(" 输入 /help 查看帮助")
|
||||
print("========================================")
|
||||
while True:
|
||||
try:
|
||||
cmd = input("\n> ").strip()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print("\n再见!")
|
||||
break
|
||||
|
||||
if not cmd:
|
||||
continue
|
||||
|
||||
if cmd == "/exit" or cmd == "exit":
|
||||
print("再见!")
|
||||
break
|
||||
elif cmd == "/get":
|
||||
print_stats()
|
||||
elif cmd == "/help":
|
||||
print_help()
|
||||
elif ">" in cmd:
|
||||
# 格式转换模式: "图片>格式 水印内容"
|
||||
parts = cmd.split(">", 1)
|
||||
image_part = parts[0].strip()
|
||||
rest = parts[1].strip()
|
||||
if not rest:
|
||||
print("缺少水印内容,请重试。输入 /help 查看帮助。")
|
||||
continue
|
||||
# 找出 ">格式 " 后面的水印文本(用 ">格式 " 分割后取第二部分)
|
||||
if " " in rest:
|
||||
final_format, text = rest.split(" ", 1)
|
||||
full_arg = f"{image_part}>{final_format}"
|
||||
else:
|
||||
print("格式参数后缺少水印内容,请重试。输入 /help 查看帮助。")
|
||||
continue
|
||||
process_single(full_arg, text)
|
||||
else:
|
||||
# 普通模式: "图片路径 水印内容"
|
||||
parts = cmd.split(None, 1)
|
||||
if len(parts) < 2:
|
||||
print("格式错误,需要提供图片路径和水印内容。输入 /help 查看帮助。")
|
||||
continue
|
||||
image_arg, text = parts
|
||||
process_single(image_arg, text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
Pillow>=10.0.0
|
||||
@@ -0,0 +1,99 @@
|
||||
import os
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from config import OUTPUT_DIR, SUPPORTED_INPUT_EXTENSIONS, SUPPORTED_OUTPUT_EXTENSIONS
|
||||
|
||||
|
||||
def load_font(size=40):
|
||||
"""根据系统加载合适的字体文件。"""
|
||||
candidates = [
|
||||
r"C:\Windows\Fonts\msyh.ttc",
|
||||
r"C:\Windows\Fonts\simhei.ttf",
|
||||
r"C:\Windows\Fonts\simsun.ttc",
|
||||
r"C:\Windows\Fonts\arial.ttf",
|
||||
]
|
||||
for path in candidates:
|
||||
if os.path.exists(path):
|
||||
return ImageFont.truetype(path, size)
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def watermark_image(input_path, text, output_path=None, output_ext=None):
|
||||
"""
|
||||
给图片添加水印并保存。
|
||||
|
||||
Args:
|
||||
input_path: 输入图片路径
|
||||
text: 水印文字内容
|
||||
output_path: 输出路径(不含扩展名),默认在 output/ 目录
|
||||
output_ext: 指定输出格式扩展名,如 ".png"、".jpg";为 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]
|
||||
|
||||
x = img.width - tw - 20
|
||||
y = img.height - th - 20
|
||||
|
||||
# 半透明背景
|
||||
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)
|
||||
|
||||
# 合成
|
||||
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()
|
||||
else:
|
||||
output_ext = output_ext.lower()
|
||||
if output_ext not in SUPPORTED_OUTPUT_EXTENSIONS:
|
||||
raise ValueError(f"不支持的输出格式: {output_ext},支持的格式: {SUPPORTED_OUTPUT_EXTENSIONS}")
|
||||
|
||||
if output_path is None:
|
||||
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)
|
||||
elif not output_path.endswith(output_ext):
|
||||
output_path = output_path + output_ext
|
||||
|
||||
# 保存
|
||||
save_kwargs = {}
|
||||
if output_ext in (".jpg", ".jpeg"):
|
||||
save_kwargs["quality"] = 95
|
||||
save_kwargs["optimize"] = True
|
||||
elif output_ext == ".png":
|
||||
save_kwargs["optimize"] = True
|
||||
|
||||
result.save(output_path, **save_kwargs)
|
||||
return os.path.abspath(output_path)
|
||||
|
||||
|
||||
def increment_count():
|
||||
"""已处理图片计数 +1。"""
|
||||
count = 0
|
||||
if os.path.exists(STATS_FILE):
|
||||
with open(STATS_FILE, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
count = int(f.read().strip())
|
||||
except ValueError:
|
||||
count = 0
|
||||
count += 1
|
||||
with open(STATS_FILE, "w", encoding="utf-8") as f:
|
||||
f.write(str(count))
|
||||
return count
|
||||
Reference in New Issue
Block a user