diff --git a/README.md b/README.md index 8857131..7bb8043 100644 --- a/README.md +++ b/README.md @@ -108,10 +108,17 @@ pip install numpy pandas opencv-python pillow openai - `topic_system_prompt`: 选题生成系统提示词文件路径 (应为要求JSON输出的版本) - `topic_user_prompt`: 选题生成基础用户提示词文件路径 - `content_system_prompt`: 内容生成系统提示词文件路径 -- `resource_dir`: 包含景点等资源文件信息的列表 (结构见 `example_config.json`) +- `resource_dir`: 包含**资源文件信息**的列表。列表中的每个元素是一个字典,包含: + - `type`: 资源类型,目前支持 `"Object"` (景点/对象信息), `"Description"` (对应的描述文件), `"Product"` (关联产品信息)。 + - `file_path`: 一个包含该类型所有资源文件**完整路径**的列表。 + - 对于 `"Object"` 类型,程序会根据选题中的对象名称在此列表中查找匹配的文件。 + - 对于 `"Description"` 类型,程序会根据选题中的对象名称在此列表中查找对应的描述文件 (文件名应包含对象名以便匹配)。 + - 对于 `"Product"` 类型,程序会根据选题中的产品名称在此列表中查找匹配的文件。 + - `num`: (可选,目前似乎未使用) 文件数量。 - `prompts_dir`: 存放 Demand/Style/Refer 等提示词片段的目录路径 - `output_dir`: 输出结果保存目录路径 -- `image_base_dir`: **图片资源根目录绝对路径或相对路径** +- `image_base_dir`: **图片资源根目录绝对路径或相对路径** (用于查找源图片) +- `poster_assets_base_dir`: **海报素材根目录绝对路径或相对路径** (用于查找字体、边框、贴纸、文本背景等) - `num`: (选题阶段)生成选题数量 - `variants`: (内容生成阶段)每个选题生成的变体数量 @@ -122,8 +129,11 @@ pip install numpy pandas opencv-python pillow openai - `content_temperature`, `content_top_p`, `content_presence_penalty`: 内容生成 API 相关参数 (默认为 0.3, 0.4, 1.5) - `request_timeout`: AI API 请求的超时时间(秒,默认 30) - `max_retries`: 请求超时或可重试网络错误时的最大重试次数(默认 3) -- `camera_image_subdir`: 存放原始照片和描述文件的子目录名(相对于 `image_base_dir`,默认"相机") -- `modify_image_subdir`: 存放处理后/用于拼贴的图片的子目录名(相对于 `image_base_dir`,默认"modify") +- `camera_image_subdir`: 存放原始照片的子目录名(相对于 `image_base_dir`,默认 "相机") - **注意:此项不再用于查找描述文件。** +- `modify_image_subdir`: 存放处理后/用于拼贴的图片的子目录名(相对于 `image_base_dir`,默认 "modify") +- `output_collage_subdir`: 在每个变体输出目录中存放拼贴图的子目录名(默认 "collage_img") +- `output_poster_subdir`: 在每个变体输出目录中存放最终海报的子目录名(默认 "poster") +- `output_poster_filename`: 输出的最终海报文件名(默认 "poster.jpg") - `poster_target_size`: 海报目标尺寸 `[宽, 高]`(默认 `[900, 1200]`) - `text_possibility`: 海报中第二段附加文字出现的概率 (默认 0.3) diff --git a/SelectPrompt/userPrompt.txt b/SelectPrompt/userPrompt.txt index e7aa0f5..8bb216d 100644 --- a/SelectPrompt/userPrompt.txt +++ b/SelectPrompt/userPrompt.txt @@ -1,4 +1,4 @@ - 选题日期注意点:不论选题数量多少,只能在我规定的时间进行选题,即你只需要罗列4月5日当天的选题,不要出现4月6日,或者5月1日,这样的日期 + 选题日期注意点:不论选题数量多少,只能在我规定的时间进行选题,即如果我给你的时间范围是4月5日,你只需要罗列4月5日当天的选题,不要出现4月6日,或者5月1日,这样的日期 选定产品:根据产品特性,随机选取我给你的产品信息,进行选题策划,但是不能自己创造不存在的信息和产品 选题风格:根据选题标题,根据我给你的各种文案提示词风格,按用户需求有逻辑的选择不同的风格和面对的用户人群即可,注意只可以在给你的资料中选择,并严谨摘取文件名称,不要自己创作 输出注意点: diff --git a/core/__pycache__/__init__.cpython-310.pyc b/core/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..3d0194e Binary files /dev/null and b/core/__pycache__/__init__.cpython-310.pyc differ diff --git a/core/__pycache__/ai_agent.cpython-310.pyc b/core/__pycache__/ai_agent.cpython-310.pyc new file mode 100644 index 0000000..a0b052f Binary files /dev/null and b/core/__pycache__/ai_agent.cpython-310.pyc differ diff --git a/core/__pycache__/posterGen.cpython-312.pyc b/core/__pycache__/posterGen.cpython-312.pyc index 83c8a4f..8ae523a 100644 Binary files a/core/__pycache__/posterGen.cpython-312.pyc and b/core/__pycache__/posterGen.cpython-312.pyc differ diff --git a/core/posterGen.py b/core/posterGen.py index dde2be7..ab26f5b 100644 --- a/core/posterGen.py +++ b/core/posterGen.py @@ -55,9 +55,13 @@ class PosterConfig: return self.config[index] class PosterGenerator: - def __init__(self,base_dir="/root/autodl-tmp/poster_baseboard_0403", output_dir=None): + def __init__(self, base_dir, output_dir=None): # 基础路径设置 + if not base_dir or not os.path.isdir(base_dir): + # Handle error: Base directory is essential + raise ValueError(f"Poster assets base directory '{base_dir}' is invalid or not provided.") self.base_dir = base_dir + print(f"Initializing PosterGenerator with asset base: {self.base_dir}") # Log the used base_dir self.font_dir = os.path.join(self.base_dir, "font") self.frame_dir = os.path.join(self.base_dir, "frames") # 边框素材目录 self.sticker_dir = os.path.join(self.base_dir, "stickers") # 贴纸素材目录 @@ -689,7 +693,7 @@ def main(): print(f"设置环境变量USE_TEXT_BG为: {os.environ['USE_TEXT_BG']}") # 创建生成器实例 - generator = PosterGenerator() + generator = PosterGenerator("/root/autodl-tmp/poster_baseboard_0403") poster_config_path = "/root/autodl-tmp/poster_generate_result/2025-04-16_20-49-32.json" poster_config = PosterConfig(poster_config_path) diff --git a/example_config.json b/example_config.json index 7c4436c..98a3b23 100644 --- a/example_config.json +++ b/example_config.json @@ -1,6 +1,6 @@ { "date": "5月15日", - "num": 5, + "num": 3, "model": "qwenQWQ", "api_url": "http://localhost:8000/v1/", "api_key": "EMPTY", @@ -10,11 +10,21 @@ "resource_dir": [ { "type": "Object", - "num": 3, + "num": 4, "file_path": [ - "./resource/Object/景点信息-泰宁古城.txt", "./resource/Object/景点信息-尚书第.txt", - "./resource/Object/景点信息-明清园.txt" + "./resource/Object/景点信息-明清园.txt", + "./resource/Object/景点信息-泰宁古城.txt", + "./resource/Object/景点信息-甘露寺.txt" + ] + }, + { + "type": "Description", + "file_path": [ + "./resource/Object/description-尚书第.txt", + "./resource/Object/description-明清园.txt", + "./resource/Object/description-泰宁古城.txt", + "./resource/Object/description-甘露寺.txt" ] }, { @@ -25,18 +35,22 @@ ], "prompts_dir": "./genPrompts", "output_dir": "./result", - "image_base_dir": "/path/to/your/image/directory", + "image_base_dir": "/root/autodl-tmp/sanming_img", + "poster_assets_base_dir": "/root/autodl-tmp/poster_baseboard_0403", "camera_image_subdir": "相机", "modify_image_subdir": "modify", - "variants": 3, + "variants": 2, "topic_temperature": 0.2, - "topic_top_p": 0.3, + "topic_top_p": 0.5, "topic_presence_penalty": 1.5, "content_temperature": 0.3, "content_top_p": 0.4, "content_presence_penalty": 1.5, "request_timeout": 30, "max_retries": 3, + "output_collage_subdir": "collage_img", + "output_poster_subdir": "poster", + "output_poster_filename": "poster.jpg", "poster_target_size": [900, 1200], "text_possibility": 0.3 } \ No newline at end of file diff --git a/examples/test_pipeline_steps.py b/examples/test_pipeline_steps.py new file mode 100644 index 0000000..2c95c45 --- /dev/null +++ b/examples/test_pipeline_steps.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import sys +import json +import traceback + +# Add project root to the Python path +project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, project_root) + +from core.ai_agent import AI_Agent +from utils.prompt_manager import PromptManager +from utils.tweet_generator import ( + run_topic_generation_pipeline, + generate_content_for_topic, + generate_posters_for_topic +) + +def load_config(config_path="/root/autodl-tmp/TravelContentCreator/poster_gen_config.json"): + """Loads configuration relative to the script.""" + if not os.path.exists(config_path): + print(f"Error: Config file '{config_path}' not found.") + sys.exit(1) + try: + with open(config_path, 'r', encoding='utf-8') as f: + config = json.load(f) + print("Configuration loaded successfully.") + return config + except Exception as e: + print(f"Error loading configuration: {e}") + sys.exit(1) + +def main_test(): + print("--- Starting Pipeline Step Test ---") + config = load_config() + + # --- Override config for faster testing --- + config['num'] = 1 # Generate only 1 topic + config['variants'] = 1 # Generate only 1 content/poster variant + print(f"Config overridden for testing: num={config['num']}, variants={config['variants']}") + + run_id = None + tweet_topic_record = None + ai_agent_content = None # Separate agent instance for content/poster + + try: + # --- Step 1: Test Topic Generation --- + print("\n--- Testing Topic Generation ---") + run_id, tweet_topic_record = run_topic_generation_pipeline(config) # run_id generated inside if not passed + + if not run_id or not tweet_topic_record or not tweet_topic_record.topics_list: + print("Topic generation failed or produced no topics. Exiting test.") + return + + print(f"Topic generation successful. Run ID: {run_id}") + print(f"Generated {len(tweet_topic_record.topics_list)} topic(s).") + test_topic = tweet_topic_record.topics_list[0] # Get the first topic for testing + print("Test Topic Data:", json.dumps(test_topic, ensure_ascii=False, indent=2)) + + # --- Step 2: Test Content Generation (for the first topic) --- + print("\n--- Testing Content Generation ---") + + # Initialize resources needed for content generation + prompt_manager = PromptManager(config) + print("Initializing AI Agent for content...") + request_timeout = config.get("request_timeout", 30) + max_retries = config.get("max_retries", 3) + ai_agent_content = AI_Agent( + config["api_url"], + config["model"], + config["api_key"], + timeout=request_timeout, + max_retries=max_retries + ) + + base_output_dir = config["output_dir"] + topic_index = 1 # Testing the first topic (1-based index) + + tweet_content_list = generate_content_for_topic( + ai_agent_content, prompt_manager, config, test_topic, + base_output_dir, run_id, topic_index + ) + + if not tweet_content_list: + print("Content generation failed or produced no content. Exiting test.") + return + + print(f"Content generation successful. Generated {len(tweet_content_list)} variant(s).") + print("Generated Content Data (first variant):", json.dumps(tweet_content_list[0], ensure_ascii=False, indent=2)) + + + # --- Step 3: Test Poster Generation (for the first topic/content) --- + print("\n--- Testing Poster Generation ---") + + # Poster generation uses its own internal ContentGenerator and PosterGenerator instances + # We just need to call the function + success = generate_posters_for_topic( + config, + test_topic, + tweet_content_list, # Pass the list generated above + base_output_dir, + run_id, + topic_index + ) + + if success: + print("Poster generation function executed (check output directory for results).") + else: + print("Poster generation function reported failure or skipped execution.") + + + except Exception as e: + print(f"\n--- An error occurred during testing ---") + print(f"Error: {e}") + traceback.print_exc() + + finally: + # Clean up the content generation AI agent if it was created + if ai_agent_content: + print("\nClosing content generation AI Agent...") + ai_agent_content.close() + print("\n--- Test Finished ---") + + +if __name__ == "__main__": + main_test() \ No newline at end of file diff --git a/poster_gen_config.json b/poster_gen_config.json index 8d44d25..862f45c 100644 --- a/poster_gen_config.json +++ b/poster_gen_config.json @@ -1,6 +1,6 @@ { "date": "5月15日", - "num": 10, + "num": 2, "model": "qwenQWQ", "api_url": "http://localhost:8000/v1/", "api_key": "EMPTY", @@ -10,11 +10,21 @@ "resource_dir": [ { "type": "Object", - "num": 3, + "num": 4, "file_path": [ "./resource/Object/景点信息-泰宁古城.txt", "./resource/Object/景点信息-尚书第.txt", - "./resource/Object/景点信息-明清园.txt" + "./resource/Object/景点信息-明清园.txt", + "./resource/Object/景点信息-甘露寺.txt" + ] + }, + { + "type": "Description", + "file_path": [ + "./resource/Object/景点信息-泰宁古城.txt", + "./resource/Object/景点信息-尚书第.txt", + "./resource/Object/景点信息-明清园.txt", + "./resource/Object/景点信息-甘露寺.txt" ] }, { @@ -26,11 +36,22 @@ "prompts_dir": "./genPrompts", "output_dir": "./result", "image_base_dir": "/root/autodl-tmp/sanming_img", + "poster_assets_base_dir": "/root/autodl-tmp/poster_baseboard_0403", "camera_image_subdir": "相机", "modify_image_subdir": "modify", - "variants": 5, + "variants": 2, "topic_temperature": 0.2, + "topic_top_p": 0.3, + "topic_presence_penalty": 1.5, "content_temperature": 0.3, + "content_top_p": 0.4, + "content_presence_penalty": 1.5, + "request_timeout": 30, + "max_retries": 3, + "description_filename": "description.txt", + "output_collage_subdir": "collage_img", + "output_poster_subdir": "poster", + "output_poster_filename": "poster.jpg", "poster_target_size": [ 900, 1200 diff --git a/utils/__pycache__/tweet_generator.cpython-312.pyc b/utils/__pycache__/tweet_generator.cpython-312.pyc index e68f878..fdd70ef 100644 Binary files a/utils/__pycache__/tweet_generator.cpython-312.pyc and b/utils/__pycache__/tweet_generator.cpython-312.pyc differ diff --git a/utils/tweet_generator.py b/utils/tweet_generator.py index 0d0c3b5..5c0e722 100644 --- a/utils/tweet_generator.py +++ b/utils/tweet_generator.py @@ -446,7 +446,14 @@ def generate_posters_for_topic(config, topic_item, tweet_content_list, output_di # Alternatively, pass initialized instances if they hold state or are expensive try: content_gen_instance = core_contentGen.ContentGenerator() - poster_gen_instance = core_posterGen.PosterGenerator() + # poster_gen_instance = core_posterGen.PosterGenerator() + # --- Read poster assets base dir from config --- + poster_assets_base_dir = config.get("poster_assets_base_dir") + if not poster_assets_base_dir: + print("Error: 'poster_assets_base_dir' not found in configuration. Cannot generate posters.") + return False # Cannot proceed without assets base dir + # --- Initialize PosterGenerator with the base dir --- + poster_gen_instance = core_posterGen.PosterGenerator(base_dir=poster_assets_base_dir) except Exception as e: print(f" Error initializing generators for poster creation: {e}") return False @@ -470,26 +477,42 @@ def generate_posters_for_topic(config, topic_item, tweet_content_list, output_di if not object_name_cleaned: print(f" Warning: Object name '{object_name}' resulted in empty string after cleaning. Skipping posters.") return False - object_name = object_name_cleaned + object_name = object_name_cleaned # Use the cleaned name for searching except Exception as e: print(f" Warning: Could not fully clean object name '{object_name}': {e}. Skipping posters.") return False - # Construct and check image paths - input_img_dir_path = os.path.join(image_base_dir, modify_image_subdir, object_name) - camera_img_dir_path = os.path.join(image_base_dir, camera_image_subdir, object_name) - description_file_path = os.path.join(camera_img_dir_path, "description.txt") - + # Construct and check INPUT image paths (still needed for collage) + input_img_dir_path = os.path.join(image_base_dir, modify_image_subdir, object_name) if not os.path.exists(input_img_dir_path) or not os.path.isdir(input_img_dir_path): - print(f" Image directory not found or not a directory: '{input_img_dir_path}'. Skipping posters for this topic.") + print(f" Modify Image directory not found or not a directory: '{input_img_dir_path}'. Skipping posters for this topic.") return False + # --- NEW: Locate Description File using resource_dir type "Description" --- info_directory = [] - if os.path.exists(description_file_path): - info_directory = [description_file_path] - print(f" Using description file: {description_file_path}") - else: - print(f" Description file not found: '{description_file_path}'. Using generated content for poster text.") + description_file_path = None + resource_dir_config = config.get("resource_dir", []) + found_description = False + + for dir_info in resource_dir_config: + if dir_info.get("type") == "Description": + for file_path in dir_info.get("file_path", []): + # Match description file based on object name containment + if object_name in os.path.basename(file_path): + description_file_path = file_path + if os.path.exists(description_file_path): + info_directory = [description_file_path] # Pass the found path + print(f" Found and using description file from config: {description_file_path}") + found_description = True + else: + print(f" Warning: Description file specified in config not found: {description_file_path}") + break # Found the matching entry in this list + if found_description: # Stop searching resource_dir if found + break + + if not found_description: + print(f" No matching description file found for object '{object_name}' in config resource_dir (type='Description').") + # --- End NEW Description File Logic --- # --- Generate Text Configurations for All Variants --- try: @@ -522,8 +545,10 @@ def generate_posters_for_topic(config, topic_item, tweet_content_list, output_di # Define output directories for this specific variant run_output_dir = os.path.join(output_dir, run_id) # Base dir for the run variant_output_dir = os.path.join(run_output_dir, f"{topic_index}_{variant_index}") - collage_output_dir = os.path.join(variant_output_dir, "collage_img") - poster_output_dir = os.path.join(variant_output_dir, "poster") + output_collage_subdir = config.get("output_collage_subdir", "collage_img") + output_poster_subdir = config.get("output_poster_subdir", "poster") + collage_output_dir = os.path.join(variant_output_dir, output_collage_subdir) + poster_output_dir = os.path.join(variant_output_dir, output_poster_subdir) os.makedirs(collage_output_dir, exist_ok=True) os.makedirs(poster_output_dir, exist_ok=True) @@ -555,8 +580,10 @@ def generate_posters_for_topic(config, topic_item, tweet_content_list, output_di if len(texts) > 1 and random.random() < text_possibility: # Use variable from config text_data["additional_texts"].append({"text": texts[1], "position": "bottom", "size_factor": 0.5}) - final_poster_path = os.path.join(poster_output_dir, "poster.jpg") - result_path = poster_gen_instance.create_poster(collage_img_path, text_data, final_poster_path) + # final_poster_path = os.path.join(poster_output_dir, "poster.jpg") # Filename "poster.jpg" is hardcoded + output_poster_filename = config.get("output_poster_filename", "poster.jpg") + final_poster_path = os.path.join(poster_output_dir, output_poster_filename) + result_path = poster_gen_instance.create_poster(collage_img_path, text_data, final_poster_path) # Uses hardcoded output filename if result_path: print(f" Successfully generated poster: {result_path}") else: