Python项目模块化开发指南
标准Python项目结构
对于一个模块化的Python项目,以下是一个广泛采用的标准结构^2:
videoforge_ai/ # 项目根目录
├── LICENSE # 项目许可证
├── README.md # 项目说明文档
├── pyproject.toml # 现代Python项目依赖管理
├── setup.py # 包安装配置
├── .env.example # 环境变量示例(不包含实际密钥)
├── .gitignore # Git忽略文件
├── requirements.txt # 依赖清单
├── videoforge_ai/ # 主源代码包
│ ├── __init__.py # 将目录声明为Python包
│ ├── __main__.py # 命令行入口点
│ ├── config.py # 配置管理
│ ├── api/ # API相关模块
│ │ ├── __init__.py
│ │ ├── openai.py # OpenAI API客户端
│ │ ├── suno.py # Suno API客户端
│ │ └── elevenlabs.py # Elevenlabs API客户端
│ ├── core/ # 核心功能模块
│ │ ├── __init__.py
│ │ ├── script_generator.py # 脚本生成
│ │ ├── image_generator.py # 图像生成
│ │ ├── tts_engine.py # 文本到语音引擎
│ │ └── music_generator.py # 音乐生成
│ ├── video/ # 视频处理模块
│ │ ├── __init__.py
│ │ ├── photo_inpainting.py # 3D Photo Inpainting
│ │ ├── composer.py # 视频合成
│ │ └── subtitles.py # 字幕生成
│ ├── utils/ # 实用工具
│ │ ├── __init__.py
│ │ ├── logger.py # 日志工具
│ │ └── file_helpers.py # 文件处理
│ └── web/ # Web服务(可选)
│ ├── __init__.py
│ ├── app.py # Flask/FastAPI应用
│ └── routes.py # 路由定义
├── tests/ # 测试目录
│ ├── __init__.py
│ ├── test_script_generator.py
│ └── test_video_composer.py
├── notebooks/ # Jupyter笔记本(研究/演示)
│ └── demo.ipynb
└── docker/ # Docker相关文件
├── Dockerfile
└── docker-compose.yml
凭证管理最佳实践
处理API密钥和敏感凭证是项目开发中的关键安全考虑事项。以下是管理凭证的推荐方法^7 ^8 ^9:
1. 使用环境变量
在本地开发中:
# config.py
import os
from dotenv import load_dotenv
# 加载.env文件中的环境变量(只在开发环境中使用)
load_dotenv()
# 获取环境变量中的凭证
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
SUNO_API_KEY = os.environ.get("SUNO_API_KEY")
ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY")
# 如果环境变量不存在,可以提供有用的错误信息
if not OPENAI_API_KEY:
raise ValueError("OPENAI_API_KEY环境变量未设置。请查看README.md获取设置说明。")
创建一个.env
文件存储本地开发的凭证(确保将其添加到.gitignore
中):
# .env文件示例
OPENAI_API_KEY=sk-your-key-here
SUNO_API_KEY=suno-key-here
ELEVENLABS_API_KEY=eleven-key-here
同时,提供一个.env.example
文件作为模板,但不包含实际密钥:
# .env.example
OPENAI_API_KEY=
SUNO_API_KEY=
ELEVENLABS_API_KEY=
2. 使用专门的密钥管理工具
对于更高级的需求,考虑使用:
- AWS Secrets Manager
- Azure Key Vault
- Google Cloud Secret Manager
- HashiCorp Vault
这些工具提供了更强的安全保障和访问控制。
容器化部署
将您的应用容器化可以简化部署并确保一致的运行环境^4:
Dockerfile示例
# docker/Dockerfile
FROM python:3.10-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
ffmpeg \
libsm6 \
libxext6 \
&& rm -rf /var/lib/apt/lists/*
# 复制项目文件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制源代码
COPY . .
# 设置Python路径
ENV PYTHONPATH=/app
# 运行命令
CMD ["python", "-m", "videoforge_ai"]
Docker Compose配置
# docker/docker-compose.yml
version: '3'
services:
app:
build:
context: ..
dockerfile: docker/Dockerfile
volumes:
- ../data:/app/data
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- SUNO_API_KEY=${SUNO_API_KEY}
- ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY}
ports:
- "5000:5000"
在容器化环境中,您可以:
- 使用Docker Secrets或环境变量注入凭证
- 避免在镜像中硬编码敏感信息
- 使用卷挂载来持久化数据
使用Cookiecutter快速开始项目
对于新手来说,使用预定义的项目模板是个好主意。Cookiecutter是一个创建项目模板的工具,可以快速生成标准化的项目结构^10:
# 安装cookiecutter
pip install cookiecutter
# 使用通用Python项目模板
cookiecutter https://github.com/audreyfeldroy/cookiecutter-pypackage
# 或者使用专为数据科学设计的模板
cookiecutter https://github.com/drivendata/cookiecutter-data-science
模块化开发的关键概念
为确保您的项目具有良好的模块性和可维护性^1,请遵循以下原则:
- 职责分离: 每个模块应该有明确的职责,避免职责重叠
- 依赖管理: 避免循环依赖,高级模块应该依赖低级模块
- 接口设计: 设计清晰的模块接口,隐藏内部实现细节
- 配置分离: 将配置与代码分离,使用配置文件或环境变量
针对VideoForge AI的具体实现
基于您的项目需求,我推荐以下文件组织和模块化实现:
主要入口点
# videoforge_ai/__main__.py
"""VideoForge AI - 自动化视频生成流水线"""
import argparse
import asyncio
from videoforge_ai.core.pipeline import generate_video
def main():
"""命令行入口点"""
parser = argparse.ArgumentParser(description='VideoForge AI - 自动化视频生成系统')
parser.add_argument('--idea', type=str, required=True, help='视频创意文本')
parser.add_argument('--output', type=str, default='output.mp4', help='输出视频路径')
args = parser.parse_args()
# 运行异步流程
result = asyncio.run(generate_video(args.idea, args.output))
print(f"视频生成完成: {result}")
if __name__ == "__main__":
main()
简化的配置模块
# videoforge_ai/config.py
"""配置管理模块"""
import os
from dotenv import load_dotenv
import logging
# 加载环境变量
load_dotenv()
# API凭证
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
SUNO_API_KEY = os.environ.get("SUNO_API_KEY")
ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY")
# 应用配置
DEFAULT_OUTPUT_DIR = os.environ.get("OUTPUT_DIR", "outputs")
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
# 确保输出目录存在
os.makedirs(DEFAULT_OUTPUT_DIR, exist_ok=True)
# 设置日志
logging.basicConfig(
level=getattr(logging, LOG_LEVEL),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 配置验证
def validate_config():
"""验证必要的配置是否存在"""
missing_keys = []
if not OPENAI_API_KEY:
missing_keys.append("OPENAI_API_KEY")
if not SUNO_API_KEY:
missing_keys.append("SUNO_API_KEY")
if not ELEVENLABS_API_KEY:
missing_keys.append("ELEVENLABS_API_KEY")
if missing_keys:
raise ValueError(f"缺少必要的环境变量: {', '.join(missing_keys)}")
return True
主要流水线实现
# videoforge_ai/core/pipeline.py
"""主要视频生成流水线"""
import asyncio
import logging
import os
from typing import List, Dict, Any
from videoforge_ai.config import DEFAULT_OUTPUT_DIR
from videoforge_ai.core.script_generator import generate_script
from videoforge_ai.core.tts_engine import generate_tts
from videoforge_ai.core.music_generator import generate_music
from videoforge_ai.core.image_generator import generate_image
from videoforge_ai.video.photo_inpainting import generate_3d_photo
from videoforge_ai.video.subtitles import generate_subtitles
from videoforge_ai.video.composer import composite_video
from videoforge_ai.utils.audio_analysis import analyze_audio_for_transitions
logger = logging.getLogger(__name__)
async def generate_video(idea_text: str, output_path: str = None) -> str:
"""完整的从创意文本到视频的生成流程
Args:
idea_text: 用户输入的创意文本
output_path: 输出视频的路径
Returns:
str: 生成的视频文件路径
"""
logger.info("开始处理视频生成请求")
# 设置默认输出路径
if not output_path:
os.makedirs(DEFAULT_OUTPUT_DIR, exist_ok=True)
output_path = os.path.join(DEFAULT_OUTPUT_DIR, f"video_{int(time.time())}.mp4")
# 1. LLM处理生成多层内容
logger.info("生成脚本和场景描述")
script_data = await generate_script(idea_text)
scenes = script_data["scenes"]
full_script = script_data["full_script"]
music_prompt = script_data["music_prompt"]
# 2. 并行处理各个组件
logger.info("开始并行生成音频和图像")
# 2.1 生成语音
audio_task = asyncio.create_task(
generate_tts(full_script)
)
# 2.2 生成背景音乐
music_task = asyncio.create_task(
generate_music(music_prompt)
)
# 2.3 分析场景并生成图像
images_tasks = []
for i, scene in enumerate(scenes):
images_tasks.append(
asyncio.create_task(
generate_image(scene["visual_description"], f"scene_{i}")
)
)
# 等待所有任务完成
audio_path = await audio_task
music_path = await music_task
image_paths = await asyncio.gather(*images_tasks)
logger.info(f"音频生成完成: {audio_path}")
logger.info(f"音乐生成完成: {music_path}")
logger.info(f"图像生成完成: {len(image_paths)}个场景")
# 3. 分析音频寻找场景转换点
logger.info("分析音频寻找场景转换点")
scene_timestamps = analyze_audio_for_transitions(audio_path, len(scenes))
# 4. 使用3D Photo Inpainting创建视差效果视频
logger.info("开始生成视差效果视频")
video_clips = []
for i, image_path in enumerate(image_paths):
# 计算每个场景的持续时间
if i < len(scene_timestamps) - 1:
duration = scene_timestamps[i+1] - scene_timestamps[i]
else:
# 最后一个场景
audio_duration = get_audio_duration(audio_path)
duration = audio_duration - scene_timestamps[i]
# videoforge_ai/video/photo_inpainting.py
temp_video_path = os.path.join(DEFAULT_OUTPUT_DIR, f"temp_scene_{i}.mp4")
logger.info(f"处理场景 {i+1}/{len(image_paths)}: 持续时间 {duration:.2f}秒")
scene_video = generate_3d_photo(
image_path,
temp_video_path,
duration=duration
)
video_clips.append(scene_video)
# 5. 生成字幕
logger.info("生成字幕文件")
subtitle_file = generate_subtitles(scenes, scene_timestamps)
# 6. 合成最终视频
logger.info("合成最终视频")
final_video = composite_video(
video_clips,
audio_path,
music_path,
subtitle_file,
output_path
)
# 7. 清理临时文件
for clip in video_clips:
if os.path.exists(clip):
os.remove(clip)
logger.info(f"视频生成完成: {final_video}")
return final_video
Python模块化开发最佳实践
根据搜索结果^1和其他开发标准,以下是一些Python项目模块化开发的核心最佳实践:
1. 包结构与命名约定
-
使用小写蛇形命名法:所有模块和包名应该使用小写字母和下划线
# 好的例子 from videoforge_ai.core.script_generator import parse_response # 不好的例子 from VideoForge.Core.ScriptGenerator import parseResponse
-
避免太深的嵌套:通常不要超过3-4层包嵌套
# 较好的结构 from videoforge_ai.core import script_generator # 而不是 from videoforge_ai.processing.text.generation.script import generator
2. 模块内部结构
每个模块应当遵循以下结构:
"""
模块的简短描述
更详细的描述...
"""
# 标准库导入
import os
import sys
import logging
# 第三方库导入
import numpy as np
import torch
from PIL import Image
# 项目内导入
from videoforge_ai.utils import file_helpers
from videoforge_ai.config import SETTINGS
# 常量
DEFAULT_WIDTH = 1920
DEFAULT_HEIGHT = 1080
# 全局变量
logger = logging.getLogger(__name__)
# 类定义
class ImageProcessor:
"""图像处理类"""
def __init__(self, width=DEFAULT_WIDTH, height=DEFAULT_HEIGHT):
"""初始化图像处理器"""
self.width = width
self.height = height
def process(self, image_path):
"""处理图像的主方法"""
pass
# 函数定义
def load_image(path):
"""加载图像文件
Args:
path: 图像文件路径
Returns:
PIL.Image: 加载的图像对象
"""
return Image.open(path)
# 主入口点(如果作为脚本运行)
if __name__ == "__main__":
# 脚本运行代码
pass
3. 模块的依赖管理
依赖声明文件
使用requirements.txt
或现代项目推荐的pyproject.toml
,示例:
# pyproject.toml
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "videoforge-ai"
version = "0.1.0"
description = "自动化智能视频生成流水线"
readme = "README.md"
authors = [
{name = "Your Name", email = "your.email@example.com"},
]
license = {text = "MIT"}
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.8"
dependencies = [
"openai>=1.0.0",
"elevenlabs>=0.2.0",
"moviepy>=1.0.3",
"numpy>=1.20.0",
"torch>=1.10.0",
"pillow>=9.0.0",
"python-dotenv>=0.19.0",
"flask>=2.0.0",
"requests>=2.27.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=22.1.0",
"isort>=5.10.0",
"mypy>=0.931",
]
[project.scripts]
videoforge = "videoforge_ai.__main__:main"
4. 使用Web框架结构化API服务
如果您计划将项目作为Web服务提供,可以使用Flask或FastAPI:
# videoforge_ai/web/app.py
from flask import Flask, request, jsonify, send_from_directory
import asyncio
import os
import uuid
import logging
from videoforge_ai.core.pipeline import generate_video
from videoforge_ai.config import DEFAULT_OUTPUT_DIR
app = Flask(__name__)
logger = logging.getLogger(__name__)
@app.route('/api/generate', methods=['POST'])
def api_generate_video():
"""API端点:生成视频"""
try:
data = request.json
if 'idea' not in data:
return jsonify({"error": "Missing 'idea' in request"}), 400
# 生成唯一ID作为输出文件名
job_id = str(uuid.uuid4())
output_path = os.path.join(DEFAULT_OUTPUT_DIR, f"{job_id}.mp4")
# 确保输出目录存在
os.makedirs(DEFAULT_OUTPUT_DIR, exist_ok=True)
# 启动异步任务处理视频生成
# 注意: Flask不原生支持asyncio,这里用线程运行
import threading
thread = threading.Thread(
target=lambda: asyncio.run(generate_video(data['idea'], output_path))
)
thread.start()
# 立即返回作业ID
return jsonify({
"job_id": job_id,
"status": "processing",
"message": "Video generation started"
})
except Exception as e:
logger.exception("视频生成请求处理失败")
return jsonify({"error": str(e)}), 500
@app.route('/api/status/<job_id>', methods=['GET'])
def check_status(job_id):
"""API端点:检查视频生成状态"""
output_path = os.path.join(DEFAULT_OUTPUT_DIR, f"{job_id}.mp4")
if os.path.exists(output_path):
return jsonify({
"job_id": job_id,
"status": "completed",
"video_url": f"/download/{job_id}.mp4"
})
else:
return jsonify({
"job_id": job_id,
"status": "processing"
})
@app.route('/download/<filename>', methods=['GET'])
def download_file(filename):
"""API端点:下载生成的视频文件"""
return send_from_directory(DEFAULT_OUTPUT_DIR, filename)
def run_server(host='0.0.0.0', port=5000):
"""运行Flask服务器"""
app.run(host=host, port=port)
if __name__ == '__main__':
run_server()
使用更现代的Python框架与实践
在更现代的Python项目中,可以考虑使用以下工具和实践^3:
1. 使用FastAPI替代Flask
FastAPI是一个高性能、异步友好的现代API框架:
# videoforge_ai/web/app_fastapi.py
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import FileResponse
import os
import uuid
import logging
from pydantic import BaseModel
from videoforge_ai.core.pipeline import generate_video
from videoforge_ai.config import DEFAULT_OUTPUT_DIR
app = FastAPI(title="VideoForge AI API", version="1.0.0")
logger = logging.getLogger(__name__)
class GenerateRequest(BaseModel):
"""视频生成请求模型"""
idea: str
output_format: str = "mp4"
class GenerateResponse(BaseModel):
"""视频生成响应模型"""
job_id: str
status: str
message: str
class StatusResponse(BaseModel):
"""状态检查响应模型"""
job_id: str
status: str
video_url: str = None
# 后台任务处理函数
async def process_video_generation(idea: str, output_path: str):
"""异步处理视频生成"""
try:
await generate_video(idea, output_path)
except Exception as e:
logger.exception(f"视频生成失败: {str(e)}")
@app.post("/api/generate", response_model=GenerateResponse)
async def api_generate_video(
request: GenerateRequest,
background_tasks: BackgroundTasks
):
"""API端点:生成视频"""
try:
# 生成唯一ID作为输出文件名
job_id = str(uuid.uuid4())
output_path = os.path.join(DEFAULT_OUTPUT_DIR, f"{job_id}.{request.output_format}")
# 确保输出目录存在
os.makedirs(DEFAULT_OUTPUT_DIR, exist_ok=True)
# 添加到后台任务
background_tasks.add_task(
process_video_generation,
request.idea,
output_path
)
# 立即返回作业ID
return {
"job_id": job_id,
"status": "processing",
"message": "Video generation started"
}
except Exception as e:
logger.exception("视频生成请求处理失败")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/status/{job_id}", response_model=StatusResponse)
async def check_status(job_id: str):
"""API端点:检查视频生成状态"""
output_path = os.path.join(DEFAULT_OUTPUT_DIR, f"{job_id}.mp4")
if os.path.exists(output_path):
return {
"job_id": job_id,
"status": "completed",
"video_url": f"/download/{job_id}.mp4"
}
else:
return {
"job_id": job_id,
"status": "processing"
}
@app.get("/download/{filename}")
async def download_file(filename: str):
"""API端点:下载生成的视频文件"""
file_path = os.path.join(DEFAULT_OUTPUT_DIR, filename)
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="File not found")
return FileResponse(file_path)
2. 使用类型注解和数据验证
Python 3.6+支持类型注解,这对大型项目非常有帮助:
# videoforge_ai/core/image_generator.py
from typing import Optional, List, Dict, Any, Union
import os
import logging
from PIL import Image
import numpy as np
import requests
from io import BytesIO
from videoforge_ai.config import OPENAI_API_KEY
from videoforge_ai.utils.file_helpers import ensure_directory
logger = logging.getLogger(__name__)
async def generate_image(
prompt: str,
output_name: Optional[str] = None,
width: int = 1024,
height: int = 1024,
model: str = "dall-e-3",
output_dir: Optional[str] = None
) -> str:
"""使用AI模型从描述生成图像
Args:
prompt: 图像描述提示
output_name: 输出文件名(不含扩展名)
width: 图像宽度(像素)
height: 图像高度(像素)
model: 使用的模型名称
output_dir: 输出目录
Returns:
str: 生成的图像文件路径
Raises:
ValueError: 如果API密钥缺失或请求失败
"""
if not OPENAI_API_KEY:
raise ValueError("缺少OpenAI API密钥")
if not output_dir:
output_dir = "images"
ensure_directory(output_dir)
if not output_name:
import time
output_name = f"image_{int(time.time())}"
output_path = os.path.join(output_dir, f"{output_name}.png")
logger.info(f"正在生成图像: '{prompt[:50]}...'")
try:
# OpenAI API调用
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
payload = {
"model": model,
"prompt": prompt,
"n": 1,
"size": f"{width}x{height}"
}
response = requests.post(
"https://api.openai.com/v1/images/generations",
headers=headers,
json=payload
)
if response.status_code != 200:
raise ValueError(f"API请求失败: {response.text}")
image_url = response.json()["data"][0]["url"]
# 下载图像
image_response = requests.get(image_url)
image = Image.open(BytesIO(image_response.content))
# 保存图像
image.save(output_path)
logger.info(f"图像已保存到: {output_path}")
return output_path
except Exception as e:
logger.exception(f"图像生成失败: {str(e)}")
raise
3. 使用日志记录工具
一个专门的日志模块使调试和监控更简单:
# videoforge_ai/utils/logger.py
import logging
import os
from logging.handlers import RotatingFileHandler
import sys
def setup_logger(
name: str,
log_file: str = None,
level: int = logging.INFO,
log_format: str = None,
max_size: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5
) -> logging.Logger:
"""配置并返回一个日志记录器
Args:
name: 日志记录器名称
log_file: 日志文件路径,如果为None则只记录到控制台
level: 日志级别
log_format: 日志格式字符串,如果为None则使用默认格式
max_size: 每个日志文件的最大字节数
backup_count: 保留的备份日志文件数
Returns:
logging.Logger: 配置好的日志记录器
"""
if log_format is None:
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
formatter = logging.Formatter(log_format)
logger = logging.getLogger(name)
logger.setLevel(level)
# 避免重复添加处理器
if logger.handlers:
return logger
# 添加控制台处理器
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# 如果指定了日志文件,添加文件处理器
if log_file:
# 确保日志目录存在
log_dir = os.path.dirname(log_file)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir)
file_handler = RotatingFileHandler(
log_file,
maxBytes=max_size,
backupCount=backup_count
)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
4. 使用异步功能组件
对于I/O密集型操作(如API调用),使用异步编程可以提高性能:
# videoforge_ai/api/openai.py
import aiohttp
import asyncio
import logging
from typing import Dict, Any, Optional, List
from videoforge_ai.config import OPENAI_API_KEY
logger = logging.getLogger(__name__)
class OpenAIClient:
"""OpenAI API客户端"""
def __init__(self, api_key: Optional[str] = None):
"""初始化OpenAI客户端
Args:
api_key: OpenAI API密钥,如果为None则使用配置中的密钥
"""
self.api_key = api_key or OPENAI_API_KEY
if not self.api_key:
raise ValueError("OpenAI API密钥未设置")
self.headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {self.api_key}"
}
self.base_url = "https://api.openai.com/v1"
async def chat_completion(
self,
messages: List[Dict[str, str]],
model: str = "gpt-4-turbo",
temperature: float = 0.7,
max_tokens: Optional[int] = None
) -> Dict[str, Any]:
"""异步调用OpenAI聊天补全API
Args:
messages: 聊天消息列表
model: 模型名称
temperature: 模型温度参数
max_tokens: 最大生成令牌数
Returns:
Dict: OpenAI API响应
"""
url = f"{self.base_url}/chat/completions"
payload = {
"model": model,
"messages": messages,
"temperature": temperature
}
if max_tokens:
payload["max_tokens"] = max_tokens
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
headers=self.headers,
json=payload
) as response:
if response.status != 200:
error_text = await response.text()
logger.error(f"OpenAI API错误: {error_text}")
raise ValueError(f"API请求失败: {error_text}")
return await response.json()
except Exception as e:
logger.exception(f"OpenAI API调用失败: {str(e)}")
raise
处理凭证的更安全方式
1. 使用专门的凭证管理器
# videoforge_ai/utils/credentials.py
import os
import logging
import json
from typing import Dict, Any, Optional
import keyring
from cryptography.fernet import Fernet
logger = logging.getLogger(__name__)
class CredentialManager:
"""安全的凭证管理器"""
def __init__(self, namespace: str = "videoforge_ai"):
"""初始化凭证管理器
Args:
namespace: 凭证命名空间
"""
self.namespace = namespace
self.env_prefix = "VIDEOFORGE_"
self.keyring_available = self._check_keyring_available()
def _check_keyring_available(self) -> bool:
"""检查系统keyring是否可用"""
try:
keyring.get_keyring()
return True
except Exception:
logger.warning("系统keyring不可用,将使用环境变量")
return False
def get_credential(self, name: str) -> Optional[str]:
"""获取凭证
按照以下顺序尝试获取凭证:
1. 环境变量
2. 系统keyring
Args:
name: 凭证名称
Returns:
str: 凭证值,如果不存在则返回None
"""
# 1. 检查环境变量
env_var = f"{self.env_prefix}{name.upper()}"
value = os.environ.get(env_var)
if value:
return value
# 2. 检查系统keyring
if self.keyring_available:
try:
value = keyring.get_password(self.namespace, name)
if value:
return value
except Exception as e:
logger.warning(f"从keyring获取凭证失败: {str(e)}")
return None
def set_credential(self, name: str, value: str) -> bool:
"""设置凭证
如果系统keyring可用,则同时保存到keyring
Args:
name: 凭证名称
value: 凭证值
Returns:
bool: 是否成功设置
"""
if not value:
logger.warning(f"尝试设置空凭证: {name}")
return False
# 保存到系统keyring
if self.keyring_available:
try:
keyring.set_password(self.namespace, name, value)
logger.info(f"凭证已保存到系统keyring: {name}")
return True
except Exception as e:
logger.warning(f"保存凭证到keyring失败: {str(e)}")
# 提示用户设置环境变量
logger.info(f"请设置环境变量 {self.env_prefix}{name.upper()}={value}")
return False
2. 更安全的配置模块
# videoforge_ai/config.py
"""配置管理模块"""
import os
import logging
from typing import Dict, Any, Optional
from dotenv import load_dotenv
from videoforge_ai.utils.credentials import CredentialManager
# 加载环境变量
load_dotenv()
# 初始化日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 初始化凭证管理器
credentials = CredentialManager()
# API凭证
OPENAI_API_KEY = credentials.get_credential("openai_api_key")
SUNO_API_KEY = credentials.get_credential("suno_api_key")
ELEVENLABS_API_KEY = credentials.get_credential("elevenlabs_api_key")
# 应用配置
DEFAULT_OUTPUT_DIR = os.environ.get("VIDEOFORGE_OUTPUT_DIR", "outputs")
LOG_LEVEL = os.environ.get("VIDEOFORGE_LOG_LEVEL", "INFO")
LOG_DIR = os.environ.get("VIDEOFORGE_LOG_DIR", "logs")
TEMP_DIR = os.environ.get("VIDEOFORGE_TEMP_DIR", "temp")
# 确保目录存在
for directory in [DEFAULT_OUTPUT_DIR, LOG_DIR, TEMP_DIR]:
os.makedirs(directory, exist_ok=True)
# 配置验证
def validate_config() -> Dict[str, bool]:
"""验证配置并返回状态
Returns:
Dict[str, bool]: 各项配置的状态
"""
status = {
"openai_api_key": bool(OPENAI_API_KEY),
"suno_api_key": bool(SUNO_API_KEY),
"elevenlabs_api_key": bool(ELEVENLABS_API_KEY)
}
missing = [key for key, value in status.items() if not value]
if missing:
logger.warning(f"缺少以下配置项: {', '.join(missing)}")
return status
命令行界面(CLI)实现
为您的项目提供良好的命令行界面,使用argparse
(内置)或click
(第三方)库:
# videoforge_ai/__main__.py
"""VideoForge AI命令行界面"""
import argparse
import asyncio
import logging
import sys
import os
from typing import List, Optional
from videoforge_ai.core.pipeline import generate_video
from videoforge_ai.config import validate_config, DEFAULT_OUTPUT_DIR
from videoforge_ai.utils.logger import setup_logger
from videoforge_ai.web.app import run_server
logger = setup_logger(
"videoforge_ai",
os.path.join("logs", "videoforge.log")
)
async def main_async(args: argparse.Namespace) -> int:
"""异步主函数
Args:
args: 命令行参数
Returns:
int: 退出码
"""
# 验证配置
config_status = validate_config()
if not all(config_status.values()):
logger.error("配置验证失败,请检查API密钥设置")
return 1
if args.command == "generate":
try:
# 生成视频
output_path = args.output or os.path.join(
DEFAULT_OUTPUT_DIR,
f"video_{int(time.time())}.mp4"
)
logger.info(f"开始生成视频: {args.idea[:50]}...")
result = await generate_video(args.idea, output_path)
logger.info(f"视频生成完成: {result}")
print(f"
成功! 视频已保存到: {result}")
return 0
except Exception as e:
logger.exception("视频生成失败")
print(f"
错误: 视频生成失败 - {str(e)}")
return 1
elif args.command == "serve":
# 运行Web服务器
logger.info(f"启动Web服务器: {args.host}:{args.port}")
print(f"启动VideoForge AI Web服务器: http://{args.host}:{args.port}")
run_server(host=args.host, port=args.port)
return 0
return 0
def main(argv: Optional[List[str]] = None) -> int:
"""命令行入口点
Args:
argv: 命令行参数列表,如果为None则使用sys.argv
Returns:
int: 退出码
"""
parser = argparse.ArgumentParser(
description='VideoForge AI - 自动化视频生成系统',
prog='videoforge'
)
subparsers = parser.add_subparsers(dest='command', help='子命令')
subparsers.required = True
# 生成视频子命令
generate_parser = subparsers.add_parser('generate', help='生成视频')
generate_parser.add_argument('--idea', '-i', type=str, required=True,
help='视频创意文本')
generate_parser.add_argument('--output', '-o', type=str, default=None,
help='输出视频路径')
# Web服务器子命令
serve_parser = subparsers.add_parser('serve', help='启动Web服务器')
serve_parser.add_argument('--host', type=str, default='0.0.0.0',
help='服务器主机地址')
serve_parser.add_argument('--port', '-p', type=int, default=5000,
help='服务器端口')
args = parser.parse_args(argv)
# 运行异步主函数
try:
return asyncio.run(main_async(args))
except KeyboardInterrupt:
print("
操作已取消")
return 130
if __name__ == "__main__":
sys.exit(main())
Docker容器化配置
针对VideoForge AI的Docker配置,提供以下文件^4:
Dockerfile
# docker/Dockerfile
FROM python:3.10-slim
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
ffmpeg \
libsm6 \
libxext6 \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
# 设置Python路径
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1
# 创建必要的目录并设置权限
RUN mkdir -p /app/outputs /app/logs /app/temp \
&& chmod -R 755 /app/outputs /app/logs /app/temp
# 暴露Web服务器端口
EXPOSE 5000
# 设置健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# 默认命令
ENTRYPOINT ["python", "-m", "videoforge_ai"]
CMD ["serve", "--host", "0.0.0.0", "--port", "5000"]
Docker Compose配置
# docker/docker-compose.yml
version: '3.8'
services:
# 主应用服务
app:
build:
context: ..
dockerfile: docker/Dockerfile
container_name: videoforge-app
volumes:
- ../outputs:/app/outputs
- ../logs:/app/logs
environment:
- VIDEOFORGE_OPENAI_API_KEY=${OPENAI_API_KEY}
- VIDEOFORGE_SUNO_API_KEY=${SUNO_API_KEY}
- VIDEOFORGE_ELEVENLABS_API_KEY=${ELEVENLABS_API_KEY}
- VIDEOFORGE_LOG_LEVEL=INFO
ports:
- "5000:5000"
restart: unless-stopped
networks:
- videoforge-network
# Nginx服务(可选,用于提供静态文件和反向代理)
nginx:
image: nginx:alpine
container_name: videoforge-nginx
volumes:
- ../outputs:/usr/share/nginx/html/outputs
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
depends_on:
- app
restart: unless-stopped
networks:
- videoforge-network
networks:
videoforge-network:
driver: bridge
Nginx配置(用于反向代理)
# docker/nginx.conf
server {
listen 80;
server_name localhost;
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# 视频文件位置
location /outputs/ {
alias /usr/share/nginx/html/outputs/;
autoindex off;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
# 适用于视频流
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 5m;
}
# API反向代理
location /api/ {
proxy_pass http://app:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# 长请求超时设置
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
}
# 健康检查端点
location /health {
proxy_pass http://app:5000/health;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
项目测试框架
一个好的项目需要适当的测试^5:
单元测试示例
# tests/test_script_generator.py
import pytest
import os
import json
from unittest.mock import patch, MagicMock
from videoforge_ai.core.script_generator import generate_script, parse_response
# 模拟OpenAI响应的样本数据
SAMPLE_LLM_RESPONSE = """
{
"full_script": "这是一个示例脚本...",
"scenes": [
{
"id": 1,
"description": "开场介绍",
"visual_description": "一个现代化城市的天际线",
"narration": "在这个数字化时代..."
},
{
"id": 2,
"description": "问题阐述",
"visual_description": "繁忙的办公室场景",
"narration": "我们每天面临着无数的决策..."
}
],
"music_prompt": "优雅的钢琴配乐,带有轻微的电子元素"
}
"""
@pytest.fixture
def mock_openai_response():
"""模拟OpenAI API响应的fixture"""
mock_response = MagicMock()
mock_response.json.return_value = {
"choices": [
{
"message": {
"content": SAMPLE_LLM_RESPONSE
}
}
]
}
return mock_response
@pytest.mark.asyncio
async def test_parse_response():
"""测试解析OpenAI响应"""
parsed = parse_response(SAMPLE_LLM_RESPONSE)
assert "full_script" in parsed
assert "scenes" in parsed
assert "music_prompt" in parsed
assert len(parsed["scenes"]) == 2
assert parsed["scenes"][0]["id"] == 1
@pytest.mark.asyncio
async def test_generate_script(mock_openai_response):
"""测试脚本生成功能"""
# 模拟OpenAI API调用
with patch('aiohttp.ClientSession.post', return_value=mock_openai_response):
result = await generate_script("创建一个关于AI的教育视频")
assert "full_script" in result
assert "scenes" in result
assert "music_prompt" in result
assert len(result["scenes"]) == 2
@pytest.mark.asyncio
async def test_generate_script_handles_error():
"""测试脚本生成错误处理"""
# 模拟API调用失败
with patch('aiohttp.ClientSession.post', side_effect=Exception("API错误")):
with pytest.raises(Exception) as excinfo:
await generate_script("测试请求")
assert "API错误" in str(excinfo.value)
集成测试示例
# tests/test_pipeline_integration.py
import pytest
import os
import shutil
import tempfile
from unittest.mock import patch, MagicMock
from videoforge_ai.core.pipeline import generate_video
# 创建临时目录fixture
@pytest.fixture
def temp_output_dir():
"""创建临时输出目录"""
temp_dir = tempfile.mkdtemp()
yield temp_dir
# 测试后清理
shutil.rmtree(temp_dir)
# 模拟各个组件
@pytest.fixture
def mock_components():
"""模拟视频生成流水线的各个组件"""
with patch('videoforge_ai.core.script_generator.generate_script') as mock_script, \
patch('videoforge_ai.core.tts_engine.generate_tts') as mock_tts, \
patch('videoforge_ai.core.music_generator.generate_music') as mock_music, \
patch('videoforge_ai.core.image_generator.generate_image') as mock_image, \
patch('videoforge_ai.video.composer.composite_video') as mock_composite:
# 配置模拟返回值
mock_script.return_value = {
"full_script": "测试脚本...",
"scenes": [
{
"id": 1,
"description": "场景1",
"visual_description": "测试场景1",
"narration": "场景1旁白"
}
],
"music_prompt": "测试音乐提示"
}
mock_tts.return_value = "/tmp/test_audio.mp3"
mock_music.return_value = "/tmp/test_music.mp3"
mock_image.return_value = "/tmp/test_image.png"
mock_composite.return_value = "/tmp/test_output.mp4"
yield {
"script": mock_script,
"tts": mock_tts,
"music": mock_music,
"image": mock_image,
"composite": mock_composite
}
@pytest.mark.asyncio
async def test_generate_video_integration(temp_output_dir, mock_components):
"""测试视频生成流水线的集成"""
output_path = os.path.join(temp_output_dir, "test_output.mp4")
# 模拟其他必要的函数
with patch('videoforge_ai.video.photo_inpainting.generate_3d_photo', return_value="/tmp/3d_scene.mp4"), \
patch('videoforge_ai.video.subtitles.generate_subtitles', return_value="/tmp/subtitles.srt"), \
patch('videoforge_ai.utils.audio_analysis.analyze_audio_for_transitions', return_value=[0, 5]):
result = await generate_video("测试创意", output_path)
# 验证各组件是否被正确调用
mock_components["script"].assert_called_once()
mock_components["tts"].assert_called_once()
mock_components["music"].assert_called_once()
mock_components["image"].assert_called_once()
mock_components["composite"].assert_called_once()
# 验证返回的输出路径
assert result == output_path
配置GitHub Actions实现CI/CD
为您的项目设置持续集成可以大大提高代码质量和部署效率^6:
# .github/workflows/python-ci.yml
name: Python CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, '3.10']
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-asyncio pytest-cov flake8 mypy black isort
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install -e .
- name: Lint with flake8
run: |
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Check formatting with black
run: |
black --check videoforge_ai tests
- name: Sort imports with isort
run: |
isort --check-only --profile black videoforge_ai tests
- name: Type check with mypy
run: |
mypy videoforge_ai
- name: Test with pytest
run: |
pytest --cov=videoforge_ai tests/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: false
build-and-push:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/Dockerfile
push: true
tags: yourusername/videoforge-ai:latest
Python项目的最佳实践总结
根据搜索结果和Python开发的标准实践,这里总结一些关键的最佳实践:
1. 遵循Python风格指南(PEP 8)
- 使用一致的缩进(4个空格)
- 每行不超过79个字符
- 使用空行分隔函数和类
- 导入放在文件顶部,分组为标准库、第三方库和本地导入
- 使用蛇形命名法(snake_case)命名变量和函数
- 使用驼峰命名法(CamelCase)命名类
2. 使用类型注解提高代码可读性和可维护性
- 为函数参数和返回值添加类型注解
- 使用mypy进行静态类型检查
- 在复杂类型上使用typing模块(List, Dict, Optional等)
3. 编写良好的文档和注释
- 为模块、类和函数编写文档字符串(docstrings)
- 使用Google或NumPy风格的docstrings
- 添加解释复杂逻辑的注释,避免解释显而易见的代码
4. 模块化设计
- 单一职责原则:每个模块、类和函数应该只有一个责任
- 依赖注入:通过参数传递依赖,而不是在函数内部创建
- 封装细节:使用私有变量和方法(_name)隐藏实现细节
5. 错误处理和日志记录
- 使用try/except捕获并处理特定异常
- 避免捕获太广泛的异常(如bare except)
- 使用日志而不是print语句,包括不同的日志级别
- 在异常处理中包含足够的上下文信息
6. 测试和质量保证
- 编写单元测试和集成测试
- 使用pytest进行测试
- 使用mocking框架模拟外部依赖
- 实施持续集成,运行测试和静态分析
7. 版本控制和依赖管理
- 使用requirements.txt或pyproject.toml管理依赖
- 考虑使用虚拟环境(venv, virtualenv)或容器(Docker)隔离环境
- 指定依赖项的版本范围,避免自动更新到不兼容版本
- 使用pip-compile或poetry等工具锁定依赖版本
8. 性能优化
- 避免过早优化,先确保代码正确和可读
- 使用性能分析工具(如cProfile)识别瓶颈
- 对I/O密集型操作使用异步编程(asyncio)
- 考虑使用多进程处理CPU密集型任务
9. 安全最佳实践
- 不要在代码中硬编码敏感信息(密码、API密钥等)
- 使用环境变量或专用凭证管理器存储敏感信息
- 验证所有外部输入,避免注入攻击
- 定期更新依赖以修复已知安全漏洞
10. 代码评审和协作
- 使用一致的代码格式化工具(如black)
- 在提交代码前进行自我评审
- 使用有意义的commit信息
- 对复杂更改创建详细的PR描述
VideoForge AI完整的项目结构
基于上述最佳实践,这是VideoForge AI项目的完整目录结构建议:
videoforge-ai/
│
├── videoforge_ai/ # 主包目录
│ ├── __init__.py # 包初始化文件
│ ├── __main__.py # CLI入口点
│ ├── config.py # 配置管理
│ │
│ ├── api/ # 外部API集成
│ │ ├── __init__.py
│ │ ├── openai.py # OpenAI API客户端
│ │ ├── elevenlabs.py # ElevenLabs API客户端
│ │ └── suno.py # Suno AI API客户端
│ │
│ ├── core/ # 核心功能模块
│ │ ├── __init__.py
│ │ ├── pipeline.py # 主视频生成流水线
│ │ ├── script_generator.py # 脚本生成模块
│ │ ├── tts_engine.py # 文本到语音引擎
│ │ ├── music_generator.py # 音乐生成模块
│ │ └── image_generator.py # 图像生成模块
│ │
│ ├── video/ # 视频处理模块
│ │ ├── __init__.py
│ │ ├── photo_inpainting.py # 3D照片处理
│ │ ├── composer.py # 视频合成器
│ │ ├── subtitles.py # 字幕生成
│ │ └── effects.py # 视频特效
│ │
│ ├── utils/ # 通用工具
│ │ ├── __init__.py
│ │ ├── logger.py # 日志工具
│ │ ├── credentials.py # 凭证管理
│ │ ├── file_helpers.py # 文件操作工具
│ │ └── audio_analysis.py # 音频分析工具
│ │
│ └── web/ # Web服务
│ ├── __init__.py
│ ├── app.py # Flask应用
│ └── app_fastapi.py # FastAPI应用(替代方案)
│
├── tests/ # 测试目录
│ ├── __init__.py # 测试包初始化
│ ├── conftest.py # pytest配置
│ ├── test_pipeline.py # 流水线测试
│ ├── test_script_generator.py # 脚本生成测试
│ └── ...
│
├── docker/ # Docker相关文件
│ ├── Dockerfile # 主Dockerfile
│ ├── docker-compose.yml # Docker Compose配置
│ └── nginx.conf # Nginx配置
│
├── docs/ # 文档
│ ├── api.md # API文档
│ ├── development.md # 开发指南
│ └── deployment.md # 部署指南
│
├── examples/ # 使用示例
│ ├── basic_video.py # 基本视频生成示例
│ └── web_service.py # Web服务使用示例
│
├── scripts/ # 实用脚本
│ ├── install.sh # 安装脚本
│ └── setup_env.py # 环境设置脚本
│
├── .github/ # GitHub配置
│ └── workflows/ # GitHub Actions
│ └── python-ci.yml # CI/CD配置
│
├── .gitignore # Git忽略文件
├── .flake8 # Flake8配置
├── .pre-commit-config.yaml # pre-commit钩子配置
├── pyproject.toml # 项目配置(Poetry)
├── requirements.txt # 依赖列表
├── requirements-dev.txt # 开发依赖
├── setup.py # 安装脚本
└── README.md # 项目说明
完整性验证清单
在完成项目实现时,可以使用以下清单验证是否遵循了最佳实践:
代码质量
- 代码遵循PEP 8风格指南
- 使用类型注解
- 有完整的文档字符串(docstrings)
- 单元测试覆盖率>80%
- 没有未处理的静态分析警告
项目结构
- 模块化设计,职责分明
- 依赖明确声明
- 文档完整且最新
- 配置分离自代码
- 敏感数据处理安全
工程实践
- CI/CD流程已配置
- Docker化已完成
- 错误处理完善
- 日志记录全面
- 有使用示例
总结
VideoForge AI的模块化实现遵循了Python最佳实践,设计了清晰的组件结构和交互方式。主要优势包括:
- 模块化架构:每个组件职责单一,便于维护和扩展
- 异步处理:使用asyncio实现并发操作,提高处理效率
- 安全性考虑:凭证管理分离,避免硬编码敏感信息
- 多平台支持:通过Docker容器化实现跨平台部署
- 完善的测试:单元测试和集成测试确保功能可靠性
- 灵活的接口:同时提供CLI和Web API,满足不同使用场景
通过这种模块化设计,VideoForge AI不仅具备了强大的视频生成能力,还保持了良好的代码质量和可扩展性,使团队能够持续迭代改进功能。