#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
WordPress 发布系统 - 图片处理模块
处理图片保存、上传到 WordPress 媒体库
"""
import os
import base64
import requests
from io import BytesIO
from PIL import Image
from modules.wp_logger import get_publish_logger, get_debug_logger
# 基础目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMP_DIR = os.path.join(BASE_DIR, 'temp')
os.makedirs(TEMP_DIR, exist_ok=True)
class ImageHandler:
"""图片处理器"""
def __init__(self, wp_url, wp_user, wp_password):
"""
初始化图片处理器
Args:
wp_url: WordPress 站点 URL
wp_user: WordPress 用户名
wp_password: WordPress 应用密码
"""
self.wp_url = wp_url.rstrip('/')
self.wp_user = wp_user
self.wp_password = wp_password
self.uploaded_images = {} # 记录已上传的图片
self.pl = get_publish_logger()
self.dl = get_debug_logger()
def save_temp_image(self, image_data, filename):
"""
保存图片到临时目录
Args:
image_data: 图片二进制数据
filename: 文件名
Returns:
str: 临时文件路径
"""
temp_path = os.path.join(TEMP_DIR, filename)
try:
with open(temp_path, 'wb') as f:
f.write(image_data)
self.dl.debug(f"图片已保存:{temp_path}")
return temp_path
except Exception as e:
self.dl.error(f"保存图片失败:{str(e)}")
raise
def upload_image(self, image_path, title=None, alt_text=None):
"""
上传图片到 WordPress 媒体库
Args:
image_path: 图片文件路径
title: 图片标题
alt_text: 图片 alt 文本
Returns:
dict: 上传结果,包含 url, id, filename
"""
if not os.path.exists(image_path):
raise FileNotFoundError(f"图片文件不存在:{image_path}")
filename = os.path.basename(image_path)
if not title:
title = os.path.splitext(filename)[0]
if not alt_text:
alt_text = title
self.pl.info(f"📤 上传图片:{filename}")
self.dl.log_step("上传图片", f"文件:{filename}")
try:
# 读取图片文件
with open(image_path, 'rb') as f:
image_content = f.read()
# 获取 content_type
content_type = self._get_content_type(image_path)
# 构建请求
headers = {
'Content-Disposition': f'attachment; filename="{filename}"',
'Content-Type': content_type,
'Content-Transfer-Encoding': 'binary'
}
# 上传图片
response = requests.post(
f'{self.wp_url}/wp-json/wp/v2/media',
auth=(self.wp_user, self.wp_password),
headers=headers,
data=image_content,
verify=False, # 跳过 SSL 验证
timeout=30
)
if response.status_code == 201:
result = response.json()
image_url = result.get('source_url', '')
image_id = result.get('id', 0)
# 更新图片标题和 alt
self._update_image_meta(image_id, title, alt_text)
self.pl.success(f"图片上传成功 - ID: {image_id}, URL: {image_url}")
self.dl.log_result("上传结果", {
'id': image_id,
'url': image_url,
'filename': filename
})
# 记录已上传的图片
self.uploaded_images[filename] = {
'id': image_id,
'url': image_url,
'title': title
}
return {
'id': image_id,
'url': image_url,
'filename': filename,
'title': title
}
else:
error_msg = response.text
self.pl.error(f"图片上传失败 - 状态码:{response.status_code}")
self.dl.error(f"上传失败:{error_msg}")
raise Exception(f"图片上传失败:{error_msg}")
except Exception as e:
self.pl.error(f"图片上传异常:{str(e)}")
self.dl.error(f"上传异常:{str(e)}", exc_info=True)
raise
def upload_images_batch(self, images):
"""
批量上传图片
Args:
images: 图片列表,每个图片包含 data, filename
Returns:
list: 上传结果列表
"""
results = []
for i, image in enumerate(images):
try:
# 保存图片到临时目录
temp_path = self.save_temp_image(image['data'], image['filename'])
# 上传图片
result = self.upload_image(
temp_path,
title=f"图片 {i+1}",
alt_text=f"文章配图 {i+1}"
)
results.append(result)
# 清理临时文件
if os.path.exists(temp_path):
os.remove(temp_path)
except Exception as e:
self.pl.error(f"图片 {i+1} 上传失败:{str(e)}")
results.append({'error': str(e), 'filename': image['filename']})
return results
def generate_image_html(self, image_url, alt_text="", width=None):
"""
生成图片 HTML 标签
Args:
image_url: 图片 URL
alt_text: alt 文本
width: 图片宽度(可选)
Returns:
str: HTML img 标签
"""
style = "max-width: 100%; height: auto; display: block; margin: 16px auto;"
if width:
style += f" max-width: {width}px;"
html = f'
'
return f'{html}'
def generate_featured_image_shortcode(self, image_url, alt_text=""):
"""
生成特色图片短代码
Args:
image_url: 图片 URL
alt_text: alt 文本
Returns:
str: 特色图片 HTML
"""
return self.generate_image_html(image_url, alt_text)
def _update_image_meta(self, image_id, title, alt_text):
"""更新图片元数据"""
try:
response = requests.post(
f'{self.wp_url}/wp-json/wp/v2/media/{image_id}',
auth=(self.wp_user, self.wp_password),
json={
'title': title,
'alt_text': alt_text
},
verify=False,
timeout=10
)
if response.status_code == 200:
self.dl.debug(f"图片元数据更新成功:ID {image_id}")
except Exception as e:
self.dl.warning(f"更新图片元数据失败:{str(e)}")
def _get_content_type(self, file_path):
"""获取图片 content_type"""
ext = os.path.splitext(file_path)[1].lower()
content_type_map = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.bmp': 'image/bmp',
'.webp': 'image/webp',
'.svg': 'image/svg+xml'
}
return content_type_map.get(ext, 'image/jpeg')
def optimize_image(self, image_path, max_width=1200, quality=85):
"""
优化图片大小
Args:
image_path: 图片路径
max_width: 最大宽度
quality: 质量 (1-100)
Returns:
str: 优化后的图片路径
"""
try:
img = Image.open(image_path)
# 获取原始尺寸
width, height = img.size
# 如果宽度超过限制,等比例缩放
if width > max_width:
ratio = max_width / width
new_height = int(height * ratio)
img = img.resize((max_width, new_height), Image.LANCZOS)
self.dl.debug(f"图片已缩放:{width}x{height} -> {max_width}x{new_height}")
# 保存优化后的图片
optimized_path = image_path.replace('.', '_optimized.')
img.save(optimized_path, quality=quality, optimize=True)
return optimized_path
except Exception as e:
self.dl.warning(f"图片优化失败:{str(e)}")
return image_path
def create_image_handler(wp_url, wp_user, wp_password):
"""创建图片处理器实例"""
return ImageHandler(wp_url, wp_user, wp_password)
if __name__ == '__main__':
import sys
if len(sys.argv) < 5:
print("用法:python wp_image_handler.py ")
sys.exit(1)
wp_url = sys.argv[1]
wp_user = sys.argv[2]
wp_password = sys.argv[3]
image_path = sys.argv[4]
handler = create_image_handler(wp_url, wp_user, wp_password)
result = handler.upload_image(image_path)
print(f"上传结果:{result}")