Files
root 1ff49a3c26 refactor: 统一使用相对导入,规范 Python 包结构
🏗️ **Python 包结构规范化**:

1. **导入方式统一**:
   - 移除所有 sys.path.append() hack 方式
   - 统一使用相对导入 (from ..config import settings)
   - 符合 Python 包管理最佳实践

2. **包结构简化**:
   - 简化 python_core/__init__.py,移除复杂依赖
   - 避免包初始化时的循环导入问题
   - 清理不必要的 try-except 导入逻辑

3. **模块运行方式**:
   - 支持标准的模块运行: python -m python_core.ai_video.video_generator
   - Rust 代码使用 -m 参数调用 Python 模块
   - 相对导入在模块运行时正常工作

4. **涉及文件修改**:
   - python_core/__init__.py: 简化包初始化
   - python_core/ai_video/video_generator.py: 相对导入
   - python_core/ai_video/cloud_storage.py: 移除 fallback 逻辑
   - python_core/ai_video/api_client.py: 统一相对导入
   - python_core/video_processing/core.py: 相对导入
   - python_core/audio_processing/core.py: 相对导入
   - python_core/utils/logger.py: 相对导入
   - python_core/services/*.py: 统一相对导入
   - src-tauri/src/commands/ai_video.rs: 使用模块运行方式

5. **代码质量提升**:
   - 移除重复的 sys.path 操作
   - 清理冗余的 try-except 导入
   - 统一的错误处理方式
   - 更清晰的模块依赖关系

 **改进效果**:
- 符合 Python 最佳实践 ✓
- 代码结构更清晰 ✓
- 易于维护和测试 ✓
- 消除 hack 式路径操作 ✓
- 支持标准模块运行 ✓

现在整个 Python 包结构规范且易于维护!
2025-07-10 14:47:32 +08:00

243 lines
8.9 KiB
Python

#!/usr/bin/env python3
"""
Video Processing Core Module
视频处理核心模块
This module provides the main video processing functionality using MoviePy and FFmpeg.
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, Any, Optional
import moviepy.editor as mp
from moviepy.video.fx import resize, crop, rotate
from moviepy.audio.fx import volumex
import ffmpeg
import cv2
import numpy as np
import sys
import os
from ..config import settings
from ..utils import setup_logger, validate_video_file
logger = setup_logger(__name__)
class VideoProcessor:
"""Main video processing class."""
def __init__(self):
self.temp_dir = settings.temp_dir
self.cache_dir = settings.cache_dir
def process_video(self, input_path: str, output_path: str, operation: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
"""
Process video with specified operation and parameters.
Args:
input_path: Path to input video file
output_path: Path to output video file
operation: Type of operation to perform
parameters: Operation-specific parameters
Returns:
Dictionary with processing results
"""
try:
if not validate_video_file(input_path):
raise ValueError(f"Invalid video file: {input_path}")
logger.info(f"Processing video: {operation} on {input_path}")
# Load video
video = mp.VideoFileClip(input_path)
# Apply operation based on type
if operation == "trim":
result_video = self._trim_video(video, parameters)
elif operation == "resize":
result_video = self._resize_video(video, parameters)
elif operation == "crop":
result_video = self._crop_video(video, parameters)
elif operation == "rotate":
result_video = self._rotate_video(video, parameters)
elif operation == "adjust_brightness":
result_video = self._adjust_brightness(video, parameters)
elif operation == "adjust_contrast":
result_video = self._adjust_contrast(video, parameters)
elif operation == "add_text":
result_video = self._add_text(video, parameters)
elif operation == "merge":
result_video = self._merge_videos(parameters)
else:
raise ValueError(f"Unknown operation: {operation}")
# Write output
result_video.write_videofile(
output_path,
codec=settings.default_video_codec,
audio_codec=settings.default_audio_codec,
fps=parameters.get("fps", settings.default_fps)
)
# Clean up
video.close()
result_video.close()
# Get output info
output_info = self._get_video_info(output_path)
return {
"status": "success",
"output_path": output_path,
"info": output_info
}
except Exception as e:
logger.error(f"Video processing failed: {str(e)}")
return {
"status": "error",
"error": str(e)
}
def _trim_video(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Trim video to specified start and end times."""
start_time = params.get("start_time", 0)
end_time = params.get("end_time", video.duration)
return video.subclip(start_time, end_time)
def _resize_video(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Resize video to specified dimensions."""
width = params.get("width")
height = params.get("height")
if width and height:
return video.fx(resize, newsize=(width, height))
elif width:
return video.fx(resize, width=width)
elif height:
return video.fx(resize, height=height)
else:
raise ValueError("Width or height must be specified for resize operation")
def _crop_video(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Crop video to specified region."""
x1 = params.get("x1", 0)
y1 = params.get("y1", 0)
x2 = params.get("x2", video.w)
y2 = params.get("y2", video.h)
return video.fx(crop, x1=x1, y1=y1, x2=x2, y2=y2)
def _rotate_video(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Rotate video by specified angle."""
angle = params.get("angle", 0)
return video.fx(rotate, angle)
def _adjust_brightness(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Adjust video brightness."""
factor = params.get("factor", 1.0)
def brightness_effect(get_frame, t):
frame = get_frame(t)
return np.clip(frame * factor, 0, 255).astype(np.uint8)
return video.fl(brightness_effect)
def _adjust_contrast(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Adjust video contrast."""
factor = params.get("factor", 1.0)
def contrast_effect(get_frame, t):
frame = get_frame(t)
return np.clip(128 + factor * (frame - 128), 0, 255).astype(np.uint8)
return video.fl(contrast_effect)
def _add_text(self, video: mp.VideoFileClip, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Add text overlay to video."""
text = params.get("text", "")
fontsize = params.get("fontsize", 50)
color = params.get("color", "white")
position = params.get("position", ("center", "bottom"))
duration = params.get("duration", video.duration)
txt_clip = mp.TextClip(text, fontsize=fontsize, color=color).set_position(position).set_duration(duration)
return mp.CompositeVideoClip([video, txt_clip])
def _merge_videos(self, params: Dict[str, Any]) -> mp.VideoFileClip:
"""Merge multiple videos."""
video_paths = params.get("video_paths", [])
if len(video_paths) < 2:
raise ValueError("At least 2 videos required for merge operation")
clips = [mp.VideoFileClip(path) for path in video_paths]
return mp.concatenate_videoclips(clips)
def _get_video_info(self, video_path: str) -> Dict[str, Any]:
"""Get video file information."""
try:
probe = ffmpeg.probe(video_path)
video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
audio_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'audio'), None)
info = {
"duration": float(probe['format']['duration']),
"size": int(probe['format']['size']),
"bitrate": int(probe['format']['bit_rate']),
}
if video_stream:
info.update({
"width": int(video_stream['width']),
"height": int(video_stream['height']),
"fps": eval(video_stream['r_frame_rate']),
"video_codec": video_stream['codec_name']
})
if audio_stream:
info.update({
"audio_codec": audio_stream['codec_name'],
"sample_rate": int(audio_stream['sample_rate']),
"channels": int(audio_stream['channels'])
})
return info
except Exception as e:
logger.error(f"Failed to get video info: {str(e)}")
return {}
def main():
"""Command line interface for video processing."""
parser = argparse.ArgumentParser(description="Video Processing Core")
parser.add_argument("--input", required=True, help="Input video file path")
parser.add_argument("--output", required=True, help="Output video file path")
parser.add_argument("--operation", required=True, help="Operation to perform")
parser.add_argument("--parameters", required=True, help="Operation parameters as JSON string")
args = parser.parse_args()
try:
parameters = json.loads(args.parameters)
processor = VideoProcessor()
result = processor.process_video(args.input, args.output, args.operation, parameters)
print(json.dumps(result))
except Exception as e:
error_result = {
"status": "error",
"error": str(e)
}
print(json.dumps(error_result))
sys.exit(1)
if __name__ == "__main__":
main()