438 lines
18 KiB
Python
438 lines
18 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
AI文案生成服务集成
|
|||
|
|
使用AI大模型为照片生成创意文案
|
|||
|
|
支持多种文案风格和用途
|
|||
|
|
支持DeepSeek和DashScope两种大模型
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import json
|
|||
|
|
import requests
|
|||
|
|
from dotenv import load_dotenv
|
|||
|
|
|
|||
|
|
# 加载环境变量
|
|||
|
|
load_dotenv()
|
|||
|
|
|
|||
|
|
class AICopywriter:
|
|||
|
|
"""AI文案生成服务类"""
|
|||
|
|
|
|||
|
|
def __init__(self, provider='deepseek'):
|
|||
|
|
"""初始化AI文案生成客户端"""
|
|||
|
|
self.provider = provider
|
|||
|
|
|
|||
|
|
if provider == 'deepseek':
|
|||
|
|
self.api_key = os.getenv('DEEPSEEK_API_KEY')
|
|||
|
|
if not self.api_key:
|
|||
|
|
raise Exception("DeepSeek API密钥未配置,请在.env文件中设置DEEPSEEK_API_KEY")
|
|||
|
|
self.base_url = "https://api.deepseek.com/v1/chat/completions"
|
|||
|
|
elif provider == 'dashscope':
|
|||
|
|
self.api_key = os.getenv('DASHSCOPE_API_KEY')
|
|||
|
|
if not self.api_key:
|
|||
|
|
raise Exception("DashScope API密钥未配置,请在.env文件中设置DASHSCOPE_API_KEY")
|
|||
|
|
else:
|
|||
|
|
raise Exception(f"不支持的AI提供商: {provider}")
|
|||
|
|
|
|||
|
|
def generate_photo_caption(self, image_description, style='creative', length='medium'):
|
|||
|
|
"""为照片生成文案"""
|
|||
|
|
try:
|
|||
|
|
if self.provider == 'deepseek':
|
|||
|
|
return self._generate_with_deepseek(image_description, style, length)
|
|||
|
|
elif self.provider == 'dashscope':
|
|||
|
|
return self._generate_with_dashscope(image_description, style, length)
|
|||
|
|
else:
|
|||
|
|
raise Exception(f"不支持的AI提供商: {self.provider}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"AI文案生成失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def _generate_with_deepseek(self, image_description, style, length):
|
|||
|
|
"""使用DeepSeek生成文案"""
|
|||
|
|
try:
|
|||
|
|
prompt = self._build_prompt(image_description, style, length)
|
|||
|
|
|
|||
|
|
headers = {
|
|||
|
|
'Authorization': f'Bearer {self.api_key}',
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data = {
|
|||
|
|
'model': 'deepseek-chat',
|
|||
|
|
'messages': [
|
|||
|
|
{
|
|||
|
|
'role': 'system',
|
|||
|
|
'content': '你是一个专业的创意文案创作助手,擅长为照片生成各种风格的创意文案。你具有丰富的文学素养和营销知识,能够根据照片内容创作出富有创意和感染力的文案。'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'role': 'user',
|
|||
|
|
'content': prompt
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
'max_tokens': 500,
|
|||
|
|
'temperature': 0.8,
|
|||
|
|
'top_p': 0.9
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response = requests.post(self.base_url, headers=headers, json=data)
|
|||
|
|
result = response.json()
|
|||
|
|
|
|||
|
|
if 'choices' in result and len(result['choices']) > 0:
|
|||
|
|
caption = result['choices'][0]['message']['content'].strip()
|
|||
|
|
# 清理可能的格式标记
|
|||
|
|
caption = caption.replace('"', '').replace('\n', ' ').strip()
|
|||
|
|
return caption
|
|||
|
|
else:
|
|||
|
|
# 如果API调用失败,使用备用文案生成
|
|||
|
|
return self._generate_fallback_caption(image_description, style, length)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
# API调用失败时使用备用方案
|
|||
|
|
return self._generate_fallback_caption(image_description, style, length)
|
|||
|
|
|
|||
|
|
def _generate_with_dashscope(self, image_description, style, length):
|
|||
|
|
"""使用DashScope生成文案"""
|
|||
|
|
try:
|
|||
|
|
url = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
|
|||
|
|
|
|||
|
|
headers = {
|
|||
|
|
'Authorization': f'Bearer {self.api_key}',
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 根据风格和长度构建提示词
|
|||
|
|
prompt = self._build_prompt(image_description, style, length)
|
|||
|
|
|
|||
|
|
data = {
|
|||
|
|
'model': 'qwen-turbo',
|
|||
|
|
'input': {
|
|||
|
|
'messages': [
|
|||
|
|
{
|
|||
|
|
'role': 'system',
|
|||
|
|
'content': '你是一个专业的文案创作助手,擅长为照片生成各种风格的创意文案。'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'role': 'user',
|
|||
|
|
'content': prompt
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
'parameters': {
|
|||
|
|
'max_tokens': 500,
|
|||
|
|
'temperature': 0.8
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response = requests.post(url, headers=headers, json=data)
|
|||
|
|
result = response.json()
|
|||
|
|
|
|||
|
|
if 'output' in result and 'text' in result['output']:
|
|||
|
|
return result['output']['text']
|
|||
|
|
else:
|
|||
|
|
# 如果API调用失败,使用备用文案生成
|
|||
|
|
return self._generate_fallback_caption(image_description, style, length)
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
# API调用失败时使用备用方案
|
|||
|
|
return self._generate_fallback_caption(image_description, style, length)
|
|||
|
|
|
|||
|
|
def _build_prompt(self, image_description, style, length):
|
|||
|
|
"""构建AI提示词"""
|
|||
|
|
|
|||
|
|
style_descriptions = {
|
|||
|
|
'creative': '创意文艺风格,富有诗意和想象力',
|
|||
|
|
'professional': '专业正式风格,简洁明了',
|
|||
|
|
'social': '社交媒体风格,活泼有趣,适合朋友圈',
|
|||
|
|
'marketing': '营销推广风格,吸引眼球,促进转化',
|
|||
|
|
'simple': '简单描述风格,直接明了',
|
|||
|
|
'emotional': '情感表达风格,温暖感人'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
length_descriptions = {
|
|||
|
|
'short': '10-20字,简洁精炼',
|
|||
|
|
'medium': '30-50字,适中长度',
|
|||
|
|
'long': '80-120字,详细描述'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
prompt = f"""
|
|||
|
|
请为以下照片内容生成{style_descriptions.get(style, '创意')}的文案,要求{length_descriptions.get(length, '适中长度')}。
|
|||
|
|
|
|||
|
|
照片内容描述:{image_description}
|
|||
|
|
|
|||
|
|
文案要求:
|
|||
|
|
1. 符合{style}风格
|
|||
|
|
2. 长度{length}
|
|||
|
|
3. 有创意,吸引人
|
|||
|
|
4. 适合社交媒体分享
|
|||
|
|
|
|||
|
|
请直接输出文案内容,不要添加其他说明。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
return prompt.strip()
|
|||
|
|
|
|||
|
|
def _generate_fallback_caption(self, image_description, style, length):
|
|||
|
|
"""备用文案生成(当AI服务不可用时)"""
|
|||
|
|
|
|||
|
|
# 基于照片描述的简单文案生成
|
|||
|
|
keywords = image_description.lower().split()
|
|||
|
|
|
|||
|
|
# 提取关键信息
|
|||
|
|
objects = []
|
|||
|
|
scenes = []
|
|||
|
|
|
|||
|
|
# 简单的关键词分类(实际应用中可以使用更复杂的NLP处理)
|
|||
|
|
object_keywords = ['人', '建筑', '天空', '树', '花', '动物', '车', '食物', '水', '山']
|
|||
|
|
scene_keywords = ['户外', '室内', '自然', '城市', '夜景', '日出', '日落', '海滩', '森林']
|
|||
|
|
|
|||
|
|
for word in keywords:
|
|||
|
|
if any(obj in word for obj in object_keywords):
|
|||
|
|
objects.append(word)
|
|||
|
|
if any(scene in word for scene in scene_keywords):
|
|||
|
|
scenes.append(word)
|
|||
|
|
|
|||
|
|
# 根据风格生成文案
|
|||
|
|
if style == 'creative':
|
|||
|
|
if scenes:
|
|||
|
|
caption = f"在{scenes[0]}的怀抱中,时光静静流淌"
|
|||
|
|
elif objects:
|
|||
|
|
caption = f"{objects[0]}的美丽瞬间,定格永恒"
|
|||
|
|
else:
|
|||
|
|
caption = "捕捉生活中的美好,让每一刻都值得珍藏"
|
|||
|
|
|
|||
|
|
elif style == 'social':
|
|||
|
|
if objects:
|
|||
|
|
caption = f"今天遇到的{objects[0]}太可爱了!分享给大家~"
|
|||
|
|
else:
|
|||
|
|
caption = "分享一张美照,希望大家喜欢!"
|
|||
|
|
|
|||
|
|
elif style == 'professional':
|
|||
|
|
if scenes and objects:
|
|||
|
|
caption = f"专业拍摄:{scenes[0]}场景中的{objects[0]}特写"
|
|||
|
|
else:
|
|||
|
|
caption = "专业摄影作品展示"
|
|||
|
|
|
|||
|
|
elif style == 'marketing':
|
|||
|
|
if objects:
|
|||
|
|
caption = f"惊艳!这个{objects[0]}你一定要看看!"
|
|||
|
|
else:
|
|||
|
|
caption = "不容错过的精彩瞬间,点击了解更多!"
|
|||
|
|
|
|||
|
|
else: # simple or emotional
|
|||
|
|
if objects:
|
|||
|
|
caption = f"美丽的{objects[0]}照片"
|
|||
|
|
else:
|
|||
|
|
caption = "一张值得分享的照片"
|
|||
|
|
|
|||
|
|
# 根据长度调整
|
|||
|
|
if length == 'long' and len(caption) < 50:
|
|||
|
|
caption += "。这张照片记录了珍贵的瞬间,展现了生活的美好,值得细细品味和珍藏。"
|
|||
|
|
elif length == 'short' and len(caption) > 20:
|
|||
|
|
# 简化长文案
|
|||
|
|
caption = caption[:20] + "..."
|
|||
|
|
|
|||
|
|
return caption
|
|||
|
|
|
|||
|
|
def generate_multiple_captions(self, image_description, count=3, style='creative'):
|
|||
|
|
"""生成多个文案选项"""
|
|||
|
|
try:
|
|||
|
|
if self.provider == 'deepseek':
|
|||
|
|
return self._generate_multiple_with_deepseek(image_description, count, style)
|
|||
|
|
elif self.provider == 'dashscope':
|
|||
|
|
return self._generate_multiple_with_dashscope(image_description, count, style)
|
|||
|
|
else:
|
|||
|
|
raise Exception(f"不支持的AI提供商: {self.provider}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"生成多个文案失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def _generate_multiple_with_deepseek(self, image_description, count=3, style='creative'):
|
|||
|
|
"""使用DeepSeek生成多个文案选项"""
|
|||
|
|
try:
|
|||
|
|
captions = []
|
|||
|
|
|
|||
|
|
# 使用不同的提示词变体生成多个文案
|
|||
|
|
prompt_variants = [
|
|||
|
|
f"请为'{image_description}'照片创作一个{style}风格的文案,要求新颖独特",
|
|||
|
|
f"基于照片内容'{image_description}',写一个{style}风格的创意文案",
|
|||
|
|
f"为这张'{image_description}'的照片设计一个{style}风格的吸引人文案"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for i in range(min(count, len(prompt_variants))):
|
|||
|
|
prompt = prompt_variants[i]
|
|||
|
|
|
|||
|
|
headers = {
|
|||
|
|
'Authorization': f'Bearer {self.api_key}',
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data = {
|
|||
|
|
'model': 'deepseek-chat',
|
|||
|
|
'messages': [
|
|||
|
|
{
|
|||
|
|
'role': 'system',
|
|||
|
|
'content': '你是专业的创意文案专家,擅长为照片创作多种风格的文案。'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'role': 'user',
|
|||
|
|
'content': prompt
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
'max_tokens': 200,
|
|||
|
|
'temperature': 0.9, # 提高温度增加多样性
|
|||
|
|
'top_p': 0.95
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response = requests.post(self.base_url, headers=headers, json=data)
|
|||
|
|
result = response.json()
|
|||
|
|
|
|||
|
|
if 'choices' in result and len(result['choices']) > 0:
|
|||
|
|
caption = result['choices'][0]['message']['content'].strip()
|
|||
|
|
caption = caption.replace('"', '').replace('\n', ' ').strip()
|
|||
|
|
|
|||
|
|
captions.append({
|
|||
|
|
'option': i + 1,
|
|||
|
|
'caption': caption,
|
|||
|
|
'style': style,
|
|||
|
|
'char_count': len(caption)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return captions
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"DeepSeek多文案生成失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def _generate_multiple_with_dashscope(self, image_description, count=3, style='creative'):
|
|||
|
|
"""使用DashScope生成多个文案选项"""
|
|||
|
|
try:
|
|||
|
|
captions = []
|
|||
|
|
|
|||
|
|
# 尝试使用不同的长度和微调风格
|
|||
|
|
lengths = ['short', 'medium', 'long']
|
|||
|
|
|
|||
|
|
for i in range(min(count, len(lengths))):
|
|||
|
|
caption = self.generate_photo_caption(image_description, style, lengths[i])
|
|||
|
|
captions.append({
|
|||
|
|
'option': i + 1,
|
|||
|
|
'caption': caption,
|
|||
|
|
'length': lengths[i],
|
|||
|
|
'char_count': len(caption)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# 如果数量不足,使用不同风格补充
|
|||
|
|
if len(captions) < count:
|
|||
|
|
additional_styles = ['social', 'professional', 'emotional']
|
|||
|
|
for i, add_style in enumerate(additional_styles):
|
|||
|
|
if len(captions) >= count:
|
|||
|
|
break
|
|||
|
|
caption = self.generate_photo_caption(image_description, add_style, 'medium')
|
|||
|
|
captions.append({
|
|||
|
|
'option': len(captions) + 1,
|
|||
|
|
'caption': caption,
|
|||
|
|
'style': add_style,
|
|||
|
|
'char_count': len(caption)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return captions
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"DashScope多文案生成失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def analyze_photo_suitability(self, image_description):
|
|||
|
|
"""分析照片适合的文案风格"""
|
|||
|
|
try:
|
|||
|
|
# 简单的风格适合性分析
|
|||
|
|
keywords = image_description.lower()
|
|||
|
|
|
|||
|
|
suitability = {
|
|||
|
|
'creative': 0,
|
|||
|
|
'professional': 0,
|
|||
|
|
'social': 0,
|
|||
|
|
'marketing': 0,
|
|||
|
|
'emotional': 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 关键词匹配(实际应用中可以使用更复杂的NLP分析)
|
|||
|
|
creative_words = ['美丽', '艺术', '创意', '独特', '梦幻']
|
|||
|
|
professional_words = ['专业', '商业', '产品', '展示', '特写']
|
|||
|
|
social_words = ['朋友', '聚会', '日常', '分享', '生活']
|
|||
|
|
marketing_words = ['促销', '优惠', '新品', '限时', '推荐']
|
|||
|
|
emotional_words = ['情感', '感动', '回忆', '温暖', '幸福']
|
|||
|
|
|
|||
|
|
for word in creative_words:
|
|||
|
|
if word in keywords:
|
|||
|
|
suitability['creative'] += 1
|
|||
|
|
|
|||
|
|
for word in professional_words:
|
|||
|
|
if word in keywords:
|
|||
|
|
suitability['professional'] += 1
|
|||
|
|
|
|||
|
|
for word in social_words:
|
|||
|
|
if word in keywords:
|
|||
|
|
suitability['social'] += 1
|
|||
|
|
|
|||
|
|
for word in marketing_words:
|
|||
|
|
if word in keywords:
|
|||
|
|
suitability['marketing'] += 1
|
|||
|
|
|
|||
|
|
for word in emotional_words:
|
|||
|
|
if word in keywords:
|
|||
|
|
suitability['emotional'] += 1
|
|||
|
|
|
|||
|
|
# 排序并返回推荐
|
|||
|
|
recommended = sorted(suitability.items(), key=lambda x: x[1], reverse=True)
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'suitability_scores': suitability,
|
|||
|
|
'recommended_styles': [style for style, score in recommended if score > 0],
|
|||
|
|
'most_suitable': recommended[0][0] if recommended[0][1] > 0 else 'creative'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"照片适合性分析失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def generate_photo_caption(image_description, style='creative', length='medium', provider='dashscope'):
|
|||
|
|
"""为照片生成文案"""
|
|||
|
|
try:
|
|||
|
|
copywriter = AICopywriter(provider)
|
|||
|
|
return copywriter.generate_photo_caption(image_description, style, length)
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"照片文案生成失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def generate_multiple_captions(image_description, count=3, style='creative', provider='dashscope'):
|
|||
|
|
"""生成多个文案选项"""
|
|||
|
|
try:
|
|||
|
|
copywriter = AICopywriter(provider)
|
|||
|
|
return copywriter.generate_multiple_captions(image_description, count, style)
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"多文案生成失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def analyze_photo_suitability(image_description, provider='dashscope'):
|
|||
|
|
"""分析照片适合的文案风格"""
|
|||
|
|
try:
|
|||
|
|
copywriter = AICopywriter(provider)
|
|||
|
|
return copywriter.analyze_photo_suitability(image_description)
|
|||
|
|
except Exception as e:
|
|||
|
|
raise Exception(f"照片适合性分析失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def check_copywriter_config(provider='deepseek'):
|
|||
|
|
"""检查AI文案生成配置是否完整"""
|
|||
|
|
try:
|
|||
|
|
if provider == 'deepseek':
|
|||
|
|
api_key = os.getenv('DEEPSEEK_API_KEY')
|
|||
|
|
if not api_key:
|
|||
|
|
return False, "DeepSeek API密钥未配置"
|
|||
|
|
|
|||
|
|
# 测试连接
|
|||
|
|
copywriter = AICopywriter(provider)
|
|||
|
|
return True, "AI文案生成配置正确(DeepSeek大模型)"
|
|||
|
|
elif provider == 'dashscope':
|
|||
|
|
api_key = os.getenv('DASHSCOPE_API_KEY')
|
|||
|
|
if not api_key:
|
|||
|
|
return False, "DashScope API密钥未配置"
|
|||
|
|
|
|||
|
|
# 测试连接
|
|||
|
|
copywriter = AICopywriter(provider)
|
|||
|
|
return True, "AI文案生成配置正确(DashScope)"
|
|||
|
|
else:
|
|||
|
|
return False, f"不支持的AI提供商: {provider}"
|
|||
|
|
except Exception as e:
|
|||
|
|
return False, f"AI文案生成配置错误: {str(e)}"
|