fix: 重构
This commit is contained in:
204
docs/scenedetect-installation.md
Normal file
204
docs/scenedetect-installation.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# PySceneDetect 依赖安装指南
|
||||
|
||||
## 📦 依赖添加
|
||||
|
||||
### 已添加到 requirements.txt
|
||||
```
|
||||
scenedetect[opencv]
|
||||
```
|
||||
|
||||
这个版本包含了OpenCV支持,提供最佳的场景检测性能。
|
||||
|
||||
## 🚀 安装方法
|
||||
|
||||
### 方法1: 使用pip直接安装 (推荐)
|
||||
```bash
|
||||
pip install scenedetect[opencv]
|
||||
```
|
||||
|
||||
### 方法2: 使用requirements.txt安装
|
||||
```bash
|
||||
pip install -r python_core/requirements.txt
|
||||
```
|
||||
|
||||
### 方法3: 使用提供的脚本
|
||||
|
||||
#### Windows:
|
||||
```cmd
|
||||
scripts\install_scenedetect.bat
|
||||
```
|
||||
|
||||
#### Linux/macOS:
|
||||
```bash
|
||||
chmod +x scripts/install_scenedetect_simple.sh
|
||||
./scripts/install_scenedetect_simple.sh
|
||||
```
|
||||
|
||||
#### Python脚本:
|
||||
```bash
|
||||
python scripts/install_dependencies.py
|
||||
```
|
||||
|
||||
## 🧪 验证安装
|
||||
|
||||
### 1. 检查导入
|
||||
```python
|
||||
import scenedetect
|
||||
print(f"PySceneDetect版本: {scenedetect.__version__}")
|
||||
```
|
||||
|
||||
### 2. 测试基本功能
|
||||
```python
|
||||
from scenedetect import VideoManager, SceneManager
|
||||
from scenedetect.detectors import ContentDetector
|
||||
|
||||
# 创建管理器
|
||||
video_manager = VideoManager([])
|
||||
scene_manager = SceneManager()
|
||||
scene_manager.add_detector(ContentDetector())
|
||||
|
||||
print("✅ PySceneDetect功能正常")
|
||||
```
|
||||
|
||||
## 🔧 集成状态
|
||||
|
||||
### media_manager.py 中的集成
|
||||
- ✅ **主要方法**: `_detect_scene_changes()` 优先使用PySceneDetect
|
||||
- ✅ **备用方案**: 如果PySceneDetect不可用,自动回退到OpenCV方法
|
||||
- ✅ **错误处理**: 完善的异常处理和日志记录
|
||||
|
||||
### 使用流程
|
||||
```python
|
||||
# 在media_manager.py中
|
||||
def _detect_scene_changes(self, file_path: str, threshold: float = 30.0) -> List[float]:
|
||||
if SCENEDETECT_AVAILABLE:
|
||||
try:
|
||||
# 使用PySceneDetect进行高精度检测
|
||||
return self._detect_with_pyscenedetect(file_path, threshold)
|
||||
except Exception as e:
|
||||
logger.error(f"PySceneDetect failed: {e}")
|
||||
# 回退到OpenCV方法
|
||||
return self._detect_scene_changes_opencv(file_path, threshold)
|
||||
else:
|
||||
# 直接使用OpenCV方法
|
||||
return self._detect_scene_changes_opencv(file_path, threshold)
|
||||
```
|
||||
|
||||
## 📊 性能对比
|
||||
|
||||
| 方法 | 准确性 | 速度 | 资源占用 | 推荐场景 |
|
||||
|------|--------|------|----------|----------|
|
||||
| PySceneDetect | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 高质量分镜 |
|
||||
| OpenCV | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 快速处理 |
|
||||
|
||||
## 🎯 配置参数
|
||||
|
||||
### PySceneDetect 参数
|
||||
```python
|
||||
# 在_detect_scene_changes中
|
||||
scene_manager.add_detector(ContentDetector(threshold=threshold))
|
||||
```
|
||||
|
||||
**threshold 参数说明**:
|
||||
- **10-20**: 高敏感度,检测更多场景变化
|
||||
- **25-35**: 中等敏感度,适合大多数情况 (默认30.0)
|
||||
- **40-50**: 低敏感度,只检测明显的场景变化
|
||||
|
||||
### 使用建议
|
||||
```python
|
||||
# 高质量视频,需要精确分镜
|
||||
scene_changes = media_manager._detect_scene_changes(video_path, threshold=25.0)
|
||||
|
||||
# 快速预览,粗略分镜
|
||||
scene_changes = media_manager._detect_scene_changes(video_path, threshold=40.0)
|
||||
```
|
||||
|
||||
## 🔍 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 1. 安装失败
|
||||
```
|
||||
ERROR: Could not find a version that satisfies the requirement scenedetect
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 更新pip
|
||||
pip install --upgrade pip
|
||||
|
||||
# 重新安装
|
||||
pip install scenedetect[opencv]
|
||||
```
|
||||
|
||||
#### 2. 导入失败
|
||||
```
|
||||
ImportError: No module named 'scenedetect'
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 检查Python环境
|
||||
which python
|
||||
pip list | grep scenedetect
|
||||
|
||||
# 重新安装
|
||||
pip install --force-reinstall scenedetect[opencv]
|
||||
```
|
||||
|
||||
#### 3. OpenCV依赖问题
|
||||
```
|
||||
ImportError: No module named 'cv2'
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 安装OpenCV
|
||||
pip install opencv-python
|
||||
|
||||
# 或者重新安装完整版本
|
||||
pip install scenedetect[opencv]
|
||||
```
|
||||
|
||||
### 检查安装状态
|
||||
```python
|
||||
# 检查PySceneDetect
|
||||
try:
|
||||
import scenedetect
|
||||
print(f"✅ PySceneDetect {scenedetect.__version__}")
|
||||
except ImportError:
|
||||
print("❌ PySceneDetect 未安装")
|
||||
|
||||
# 检查OpenCV
|
||||
try:
|
||||
import cv2
|
||||
print(f"✅ OpenCV {cv2.__version__}")
|
||||
except ImportError:
|
||||
print("❌ OpenCV 未安装")
|
||||
```
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [PySceneDetect 官方文档](https://pyscenedetect.readthedocs.io/)
|
||||
- [PySceneDetect GitHub](https://github.com/Breakthrough/PySceneDetect)
|
||||
- [场景检测测试脚本](../scripts/test_scene_detection.py)
|
||||
|
||||
## 🎉 安装完成后
|
||||
|
||||
安装成功后,系统将:
|
||||
|
||||
1. **自动使用PySceneDetect**: 新导入的视频将使用更准确的场景检测
|
||||
2. **保持兼容性**: 如果PySceneDetect不可用,自动回退到OpenCV
|
||||
3. **提升分镜质量**: 更准确的场景边界检测
|
||||
4. **支持多种视频格式**: 更好的格式兼容性
|
||||
|
||||
### 验证效果
|
||||
重新导入视频文件,观察日志输出:
|
||||
```
|
||||
INFO: PySceneDetect found 5 scene changes in video
|
||||
DEBUG: Scene change timestamps: [0.0, 15.2, 32.8, 48.1, 65.3, 78.9]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*PySceneDetect 依赖安装完成,享受更准确的视频场景检测!*
|
||||
@@ -28,7 +28,7 @@ class Settings(BaseSettings):
|
||||
project_root: Path = project_root
|
||||
temp_dir: Path = Field(default_factory=lambda: project_root / ".mixvideo" / "temp")
|
||||
cache_dir: Path = Field(default_factory=lambda: project_root / ".mixvideo" / "cache")
|
||||
projects_dir: Path = Field(default_factory=lambda: project_root / "MixVideoProjects")
|
||||
projects_dir: Path = Field(default_factory=lambda: project_root / ".mixvideo"/"MixVideoProjects")
|
||||
|
||||
# Video Processing
|
||||
max_video_resolution: str = "1920x1080"
|
||||
|
||||
@@ -17,24 +17,369 @@ from python_core.config import settings
|
||||
from python_core.utils.logger import logger
|
||||
from python_core.utils.jsonrpc import create_response_handler
|
||||
|
||||
# 视频处理库
|
||||
try:
|
||||
import cv2
|
||||
import numpy as np
|
||||
VIDEO_LIBS_AVAILABLE = True
|
||||
except ImportError:
|
||||
logger.warning("Video processing libraries not available. Install opencv-python for full functionality.")
|
||||
VIDEO_LIBS_AVAILABLE = False
|
||||
# 依赖管理器 - 遵循SOLID原则
|
||||
class DependencyManager:
|
||||
"""依赖管理器 - 单一职责原则(SRP): 只负责管理依赖"""
|
||||
|
||||
# PySceneDetect库
|
||||
try:
|
||||
from scenedetect import VideoManager, SceneManager
|
||||
from scenedetect.detectors import ContentDetector
|
||||
SCENEDETECT_AVAILABLE = True
|
||||
logger.info("PySceneDetect is available for advanced scene detection")
|
||||
except ImportError:
|
||||
logger.warning("PySceneDetect not available. Install scenedetect for better scene detection.")
|
||||
SCENEDETECT_AVAILABLE = False
|
||||
def __init__(self):
|
||||
self._dependencies = {}
|
||||
self._initialize_dependencies()
|
||||
|
||||
def _initialize_dependencies(self):
|
||||
"""初始化所有依赖"""
|
||||
self._check_opencv_dependency()
|
||||
self._check_scenedetect_dependency()
|
||||
|
||||
def _check_opencv_dependency(self):
|
||||
"""检查OpenCV依赖"""
|
||||
try:
|
||||
import cv2
|
||||
import numpy as np
|
||||
self._dependencies['opencv'] = {
|
||||
'available': True,
|
||||
'modules': {'cv2': cv2, 'numpy': np},
|
||||
'version': cv2.__version__
|
||||
}
|
||||
logger.info(f"OpenCV {cv2.__version__} is available")
|
||||
except ImportError as e:
|
||||
self._dependencies['opencv'] = {
|
||||
'available': False,
|
||||
'error': str(e),
|
||||
'modules': {}
|
||||
}
|
||||
logger.warning("OpenCV not available. Install opencv-python for full functionality.")
|
||||
|
||||
def _check_scenedetect_dependency(self):
|
||||
"""检查PySceneDetect依赖"""
|
||||
try:
|
||||
from scenedetect import VideoManager, SceneManager
|
||||
from scenedetect.detectors import ContentDetector
|
||||
import scenedetect
|
||||
|
||||
self._dependencies['scenedetect'] = {
|
||||
'available': True,
|
||||
'modules': {
|
||||
'VideoManager': VideoManager,
|
||||
'SceneManager': SceneManager,
|
||||
'ContentDetector': ContentDetector
|
||||
},
|
||||
'version': scenedetect.__version__
|
||||
}
|
||||
logger.info(f"PySceneDetect {scenedetect.__version__} is available for advanced scene detection")
|
||||
except ImportError as e:
|
||||
self._dependencies['scenedetect'] = {
|
||||
'available': False,
|
||||
'error': str(e),
|
||||
'modules': {}
|
||||
}
|
||||
logger.warning("PySceneDetect not available. Install scenedetect for better scene detection.")
|
||||
|
||||
def is_available(self, dependency_name: str) -> bool:
|
||||
"""检查依赖是否可用 - 开闭原则(OCP): 对扩展开放"""
|
||||
return self._dependencies.get(dependency_name, {}).get('available', False)
|
||||
|
||||
def get_module(self, dependency_name: str, module_name: str):
|
||||
"""获取依赖模块"""
|
||||
if not self.is_available(dependency_name):
|
||||
raise ImportError(f"Dependency '{dependency_name}' is not available")
|
||||
|
||||
modules = self._dependencies[dependency_name]['modules']
|
||||
if module_name not in modules:
|
||||
raise ImportError(f"Module '{module_name}' not found in dependency '{dependency_name}'")
|
||||
|
||||
return modules[module_name]
|
||||
|
||||
def get_dependency_info(self, dependency_name: str) -> dict:
|
||||
"""获取依赖信息"""
|
||||
return self._dependencies.get(dependency_name, {})
|
||||
|
||||
def get_all_dependencies(self) -> dict:
|
||||
"""获取所有依赖信息"""
|
||||
return self._dependencies.copy()
|
||||
|
||||
# 抽象接口 - 接口隔离原则(ISP): 定义专门的接口
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class VideoInfoExtractor(ABC):
|
||||
"""视频信息提取器接口"""
|
||||
|
||||
@abstractmethod
|
||||
def extract_video_info(self, file_path: str) -> Dict:
|
||||
"""提取视频信息"""
|
||||
pass
|
||||
|
||||
class SceneDetector(ABC):
|
||||
"""场景检测器接口"""
|
||||
|
||||
@abstractmethod
|
||||
def detect_scenes(self, file_path: str, threshold: float = 30.0) -> List[float]:
|
||||
"""检测场景变化点"""
|
||||
pass
|
||||
|
||||
class VideoProcessor(ABC):
|
||||
"""视频处理器接口"""
|
||||
|
||||
@abstractmethod
|
||||
def split_video(self, input_path: str, output_dir: str, scene_times: List[float]) -> List[str]:
|
||||
"""分割视频"""
|
||||
pass
|
||||
|
||||
# 具体实现类 - 单一职责原则(SRP): 每个类只负责一个功能
|
||||
|
||||
class FFProbeVideoInfoExtractor(VideoInfoExtractor):
|
||||
"""使用FFProbe提取视频信息"""
|
||||
|
||||
def extract_video_info(self, file_path: str) -> Dict:
|
||||
"""使用ffprobe获取准确的视频信息"""
|
||||
file_size = os.path.getsize(file_path)
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
cmd = [
|
||||
'ffprobe',
|
||||
'-v', 'quiet',
|
||||
'-print_format', 'json',
|
||||
'-show_format',
|
||||
'-show_streams',
|
||||
file_path
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
|
||||
if result.returncode == 0:
|
||||
probe_data = json.loads(result.stdout)
|
||||
|
||||
video_stream = None
|
||||
for stream in probe_data.get('streams', []):
|
||||
if stream.get('codec_type') == 'video':
|
||||
video_stream = stream
|
||||
break
|
||||
|
||||
if video_stream:
|
||||
duration = float(probe_data.get('format', {}).get('duration', 0))
|
||||
width = int(video_stream.get('width', 0))
|
||||
height = int(video_stream.get('height', 0))
|
||||
|
||||
fps_str = video_stream.get('r_frame_rate', '0/1')
|
||||
if '/' in fps_str:
|
||||
num, den = fps_str.split('/')
|
||||
fps = float(num) / float(den) if float(den) != 0 else 0
|
||||
else:
|
||||
fps = float(fps_str)
|
||||
|
||||
frame_count = int(duration * fps) if fps > 0 else 0
|
||||
|
||||
logger.info(f"ffprobe video info: duration={duration:.2f}s, fps={fps:.2f}, resolution={width}x{height}")
|
||||
|
||||
return {
|
||||
'duration': duration,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'fps': fps,
|
||||
'frame_count': frame_count,
|
||||
'file_size': file_size,
|
||||
'codec': video_stream.get('codec_name', 'unknown')
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"ffprobe failed: {e}")
|
||||
raise
|
||||
|
||||
raise Exception("Failed to extract video info with ffprobe")
|
||||
|
||||
class OpenCVVideoInfoExtractor(VideoInfoExtractor):
|
||||
"""使用OpenCV提取视频信息"""
|
||||
|
||||
def __init__(self, dependency_manager: DependencyManager):
|
||||
self.dependency_manager = dependency_manager
|
||||
|
||||
def extract_video_info(self, file_path: str) -> Dict:
|
||||
"""使用OpenCV获取视频信息"""
|
||||
if not self.dependency_manager.is_available('opencv'):
|
||||
raise Exception("OpenCV not available")
|
||||
|
||||
cv2 = self.dependency_manager.get_module('opencv', 'cv2')
|
||||
file_size = os.path.getsize(file_path)
|
||||
|
||||
try:
|
||||
cap = cv2.VideoCapture(file_path)
|
||||
|
||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
duration = frame_count / fps if fps > 0 else 0
|
||||
|
||||
cap.release()
|
||||
|
||||
logger.info(f"OpenCV video info: duration={duration:.2f}s, fps={fps:.2f}, resolution={width}x{height}")
|
||||
|
||||
return {
|
||||
'duration': duration,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'fps': fps,
|
||||
'frame_count': frame_count,
|
||||
'file_size': file_size,
|
||||
'codec': 'unknown'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get video info with OpenCV: {e}")
|
||||
raise
|
||||
|
||||
class PySceneDetectSceneDetector(SceneDetector):
|
||||
"""使用PySceneDetect进行场景检测"""
|
||||
|
||||
def __init__(self, dependency_manager: DependencyManager):
|
||||
self.dependency_manager = dependency_manager
|
||||
|
||||
def detect_scenes(self, file_path: str, threshold: float = 30.0) -> List[float]:
|
||||
"""使用PySceneDetect检测场景变化点"""
|
||||
if not self.dependency_manager.is_available('scenedetect'):
|
||||
raise Exception("PySceneDetect not available")
|
||||
|
||||
VideoManager = self.dependency_manager.get_module('scenedetect', 'VideoManager')
|
||||
SceneManager = self.dependency_manager.get_module('scenedetect', 'SceneManager')
|
||||
ContentDetector = self.dependency_manager.get_module('scenedetect', 'ContentDetector')
|
||||
|
||||
try:
|
||||
video_manager = VideoManager([file_path])
|
||||
scene_manager = SceneManager()
|
||||
scene_manager.add_detector(ContentDetector(threshold=threshold))
|
||||
|
||||
video_manager.start()
|
||||
scene_manager.detect_scenes(frame_source=video_manager)
|
||||
|
||||
scene_list = scene_manager.get_scene_list()
|
||||
|
||||
scene_changes = [0.0]
|
||||
for scene in scene_list:
|
||||
start_time = scene[0].get_seconds()
|
||||
end_time = scene[1].get_seconds()
|
||||
|
||||
if start_time > 0 and start_time not in scene_changes:
|
||||
scene_changes.append(start_time)
|
||||
|
||||
if end_time not in scene_changes:
|
||||
scene_changes.append(end_time)
|
||||
|
||||
scene_changes = sorted(list(set(scene_changes)))
|
||||
video_manager.release()
|
||||
|
||||
logger.info(f"PySceneDetect found {len(scene_changes)-1} scene changes in video")
|
||||
logger.debug(f"Scene change timestamps: {scene_changes}")
|
||||
|
||||
return scene_changes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"PySceneDetect failed: {e}")
|
||||
raise
|
||||
|
||||
class OpenCVSceneDetector(SceneDetector):
|
||||
"""使用OpenCV进行场景检测"""
|
||||
|
||||
def __init__(self, dependency_manager: DependencyManager):
|
||||
self.dependency_manager = dependency_manager
|
||||
|
||||
def detect_scenes(self, file_path: str, threshold: float = 30.0) -> List[float]:
|
||||
"""使用OpenCV检测场景变化点"""
|
||||
if not self.dependency_manager.is_available('opencv'):
|
||||
raise Exception("OpenCV not available")
|
||||
|
||||
cv2 = self.dependency_manager.get_module('opencv', 'cv2')
|
||||
np = self.dependency_manager.get_module('opencv', 'numpy')
|
||||
|
||||
try:
|
||||
cap = cv2.VideoCapture(file_path)
|
||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
|
||||
if fps <= 0:
|
||||
cap.release()
|
||||
logger.warning(f"Invalid fps ({fps}) for video {file_path}")
|
||||
return [0.0]
|
||||
|
||||
scene_changes = [0.0]
|
||||
prev_frame = None
|
||||
frame_count = 0
|
||||
frame_skip = max(1, int(fps / 2))
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
break
|
||||
|
||||
if frame_count % frame_skip == 0:
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
gray = cv2.resize(gray, (320, 240))
|
||||
|
||||
if prev_frame is not None:
|
||||
diff = cv2.absdiff(prev_frame, gray)
|
||||
mean_diff = np.mean(diff)
|
||||
|
||||
if mean_diff > threshold:
|
||||
timestamp = frame_count / fps
|
||||
if not scene_changes or timestamp - scene_changes[-1] > 1.0:
|
||||
scene_changes.append(timestamp)
|
||||
logger.debug(f"Scene change detected at {timestamp:.2f}s (diff: {mean_diff:.2f})")
|
||||
|
||||
prev_frame = gray
|
||||
|
||||
frame_count += 1
|
||||
|
||||
cap.release()
|
||||
|
||||
duration = frame_count / fps if fps > 0 else 0
|
||||
if duration > 0 and (not scene_changes or duration - scene_changes[-1] > 0.5):
|
||||
scene_changes.append(duration)
|
||||
|
||||
logger.info(f"OpenCV detected {len(scene_changes)-1} scene changes in video")
|
||||
return scene_changes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to detect scene changes with OpenCV: {e}")
|
||||
raise
|
||||
|
||||
# 工厂类 - 依赖倒置原则(DIP): 依赖抽象而不是具体实现
|
||||
class VideoProcessorFactory:
|
||||
"""视频处理器工厂 - 单一职责原则(SRP): 只负责创建对象"""
|
||||
|
||||
def __init__(self, dependency_manager: DependencyManager):
|
||||
self.dependency_manager = dependency_manager
|
||||
|
||||
def create_video_info_extractor(self) -> VideoInfoExtractor:
|
||||
"""创建视频信息提取器 - 开闭原则(OCP): 对扩展开放"""
|
||||
# 优先使用FFProbe,回退到OpenCV
|
||||
try:
|
||||
return FFProbeVideoInfoExtractor()
|
||||
except Exception:
|
||||
if self.dependency_manager.is_available('opencv'):
|
||||
return OpenCVVideoInfoExtractor(self.dependency_manager)
|
||||
else:
|
||||
raise Exception("No video info extractor available")
|
||||
|
||||
def create_scene_detector(self) -> SceneDetector:
|
||||
"""创建场景检测器"""
|
||||
# 优先使用PySceneDetect,回退到OpenCV
|
||||
if self.dependency_manager.is_available('scenedetect'):
|
||||
try:
|
||||
return PySceneDetectSceneDetector(self.dependency_manager)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.dependency_manager.is_available('opencv'):
|
||||
return OpenCVSceneDetector(self.dependency_manager)
|
||||
else:
|
||||
raise Exception("No scene detector available")
|
||||
|
||||
# 全局依赖管理器实例
|
||||
dependency_manager = DependencyManager()
|
||||
video_processor_factory = VideoProcessorFactory(dependency_manager)
|
||||
|
||||
# 向后兼容的全局变量 - 里氏替换原则(LSP): 保持接口兼容
|
||||
VIDEO_LIBS_AVAILABLE = dependency_manager.is_available('opencv')
|
||||
SCENEDETECT_AVAILABLE = dependency_manager.is_available('scenedetect')
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -87,27 +432,71 @@ class OriginalVideo:
|
||||
|
||||
|
||||
class MediaManager:
|
||||
"""媒体库管理器"""
|
||||
|
||||
def __init__(self):
|
||||
"""媒体库管理器
|
||||
|
||||
遵循SOLID原则:
|
||||
- SRP: 只负责媒体文件的管理和协调
|
||||
- OCP: 通过依赖注入支持扩展
|
||||
- LSP: 可以替换不同的处理器实现
|
||||
- ISP: 使用专门的接口
|
||||
- DIP: 依赖抽象接口而不是具体实现
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
dependency_manager: DependencyManager = None,
|
||||
video_info_extractor: VideoInfoExtractor = None,
|
||||
scene_detector: SceneDetector = None):
|
||||
"""
|
||||
初始化媒体管理器
|
||||
|
||||
Args:
|
||||
dependency_manager: 依赖管理器
|
||||
video_info_extractor: 视频信息提取器
|
||||
scene_detector: 场景检测器
|
||||
"""
|
||||
self.cache_dir = settings.temp_dir / "cache"
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
# 数据文件
|
||||
self.segments_file = self.cache_dir / "video_segments.json"
|
||||
self.original_videos_file = self.cache_dir / "original_videos.json"
|
||||
|
||||
|
||||
# 依赖注入 - 依赖倒置原则(DIP)
|
||||
self.dependency_manager = dependency_manager or globals()['dependency_manager']
|
||||
self.factory = VideoProcessorFactory(self.dependency_manager)
|
||||
|
||||
# 延迟初始化处理器 - 单一职责原则(SRP)
|
||||
self._video_info_extractor = video_info_extractor
|
||||
self._scene_detector = scene_detector
|
||||
|
||||
# 加载数据
|
||||
self.video_segments = self._load_video_segments()
|
||||
self.original_videos = self._load_original_videos()
|
||||
|
||||
|
||||
# 存储目录
|
||||
self.video_storage_dir = settings.temp_dir / "video_storage"
|
||||
|
||||
@property
|
||||
def video_info_extractor(self) -> VideoInfoExtractor:
|
||||
"""获取视频信息提取器 - 懒加载"""
|
||||
if self._video_info_extractor is None:
|
||||
self._video_info_extractor = self.factory.create_video_info_extractor()
|
||||
return self._video_info_extractor
|
||||
|
||||
@property
|
||||
def scene_detector(self) -> SceneDetector:
|
||||
"""获取场景检测器 - 懒加载"""
|
||||
if self._scene_detector is None:
|
||||
self._scene_detector = self.factory.create_scene_detector()
|
||||
return self._scene_detector
|
||||
|
||||
def _initialize_directories(self):
|
||||
"""初始化目录结构 - 单一职责原则(SRP)"""
|
||||
self.video_storage_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
self.segments_dir = settings.temp_dir / "video_segments"
|
||||
self.segments_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
self.thumbnails_dir = settings.temp_dir / "video_thumbnails"
|
||||
self.thumbnails_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -174,176 +563,38 @@ class MediaManager:
|
||||
return hash_md5.hexdigest()
|
||||
|
||||
def _get_video_info(self, file_path: str) -> Dict:
|
||||
"""获取视频基本信息"""
|
||||
if not VIDEO_LIBS_AVAILABLE:
|
||||
# 如果没有视频处理库,返回基本信息
|
||||
file_size = os.path.getsize(file_path)
|
||||
return {
|
||||
'duration': 0.0,
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'fps': 0.0,
|
||||
'frame_count': 0,
|
||||
'file_size': file_size
|
||||
}
|
||||
|
||||
"""获取视频基本信息 - 使用依赖注入的提取器"""
|
||||
try:
|
||||
cap = cv2.VideoCapture(file_path)
|
||||
|
||||
# 获取视频属性
|
||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
duration = frame_count / fps if fps > 0 else 0
|
||||
|
||||
cap.release()
|
||||
|
||||
return {
|
||||
'duration': duration,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'fps': fps,
|
||||
'frame_count': frame_count,
|
||||
'file_size': os.path.getsize(file_path)
|
||||
}
|
||||
return self.video_info_extractor.extract_video_info(file_path)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get video info: {e}")
|
||||
file_size = os.path.getsize(file_path)
|
||||
# 返回基本信息作为后备
|
||||
file_size = os.path.getsize(file_path) if os.path.exists(file_path) else 0
|
||||
return {
|
||||
'duration': 0.0,
|
||||
'width': 0,
|
||||
'height': 0,
|
||||
'fps': 0.0,
|
||||
'frame_count': 0,
|
||||
'file_size': file_size
|
||||
'file_size': file_size,
|
||||
'codec': 'unknown'
|
||||
}
|
||||
|
||||
def _detect_scene_changes(self, file_path: str, threshold: float = 30.0) -> List[float]:
|
||||
"""使用PySceneDetect检测场景变化点(转场镜头)"""
|
||||
if SCENEDETECT_AVAILABLE:
|
||||
try:
|
||||
# 创建视频管理器
|
||||
video_manager = VideoManager([file_path])
|
||||
scene_manager = SceneManager()
|
||||
|
||||
# 添加内容检测器,threshold参数控制敏感度
|
||||
scene_manager.add_detector(ContentDetector(threshold=threshold))
|
||||
|
||||
# 开始检测
|
||||
video_manager.start()
|
||||
scene_manager.detect_scenes(frame_source=video_manager)
|
||||
|
||||
# 获取场景列表
|
||||
scene_list = scene_manager.get_scene_list()
|
||||
|
||||
# 转换为时间戳列表
|
||||
scene_changes = [0.0] # 开始时间
|
||||
|
||||
for scene in scene_list:
|
||||
start_time = scene[0].get_seconds()
|
||||
end_time = scene[1].get_seconds()
|
||||
|
||||
# 添加场景开始时间(跳过第一个,因为已经有0.0了)
|
||||
if start_time > 0 and start_time not in scene_changes:
|
||||
scene_changes.append(start_time)
|
||||
|
||||
# 添加场景结束时间
|
||||
if end_time not in scene_changes:
|
||||
scene_changes.append(end_time)
|
||||
|
||||
# 确保时间戳排序
|
||||
scene_changes = sorted(list(set(scene_changes)))
|
||||
|
||||
video_manager.release()
|
||||
|
||||
logger.info(f"PySceneDetect found {len(scene_changes)-1} scene changes in video")
|
||||
logger.debug(f"Scene change timestamps: {scene_changes}")
|
||||
|
||||
return scene_changes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"PySceneDetect failed: {e}, falling back to OpenCV method")
|
||||
return self._detect_scene_changes_opencv(file_path, threshold)
|
||||
else:
|
||||
logger.warning("PySceneDetect not available, using OpenCV method")
|
||||
return self._detect_scene_changes_opencv(file_path, threshold)
|
||||
|
||||
def _detect_scene_changes_opencv(self, file_path: str, threshold: float = 30.0) -> List[float]:
|
||||
"""使用OpenCV检测场景变化点(备用方案)"""
|
||||
if not VIDEO_LIBS_AVAILABLE:
|
||||
logger.warning("Video processing not available, returning basic scene changes")
|
||||
# 获取视频时长,返回开始和结束时间
|
||||
try:
|
||||
video_info = self._get_video_info(file_path)
|
||||
duration = video_info.get('duration', 0)
|
||||
return [0.0, duration] if duration > 0 else [0.0]
|
||||
except:
|
||||
return [0.0]
|
||||
|
||||
"""检测场景变化点 - 使用依赖注入的检测器"""
|
||||
try:
|
||||
cap = cv2.VideoCapture(file_path)
|
||||
fps = cap.get(cv2.CAP_PROP_FPS)
|
||||
|
||||
if fps <= 0:
|
||||
cap.release()
|
||||
logger.warning(f"Invalid fps ({fps}) for video {file_path}")
|
||||
return [0.0]
|
||||
|
||||
scene_changes = [0.0] # 开始时间
|
||||
prev_frame = None
|
||||
frame_count = 0
|
||||
|
||||
# 每隔几帧检测一次,提高性能
|
||||
frame_skip = max(1, int(fps / 2)) # 每秒检测2次
|
||||
|
||||
while True:
|
||||
ret, frame = cap.read()
|
||||
if not ret:
|
||||
break
|
||||
|
||||
# 跳帧处理
|
||||
if frame_count % frame_skip == 0:
|
||||
# 转换为灰度图并缩小尺寸以提高性能
|
||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||
gray = cv2.resize(gray, (320, 240))
|
||||
|
||||
if prev_frame is not None:
|
||||
# 计算帧间差异
|
||||
diff = cv2.absdiff(prev_frame, gray)
|
||||
mean_diff = np.mean(diff)
|
||||
|
||||
# 如果差异超过阈值,认为是场景变化
|
||||
if mean_diff > threshold:
|
||||
timestamp = frame_count / fps
|
||||
# 避免过于接近的场景变化点
|
||||
if not scene_changes or timestamp - scene_changes[-1] > 1.0:
|
||||
scene_changes.append(timestamp)
|
||||
logger.debug(f"Scene change detected at {timestamp:.2f}s (diff: {mean_diff:.2f})")
|
||||
|
||||
prev_frame = gray
|
||||
|
||||
frame_count += 1
|
||||
|
||||
cap.release()
|
||||
|
||||
# 添加结束时间
|
||||
duration = frame_count / fps if fps > 0 else 0
|
||||
if duration > 0 and (not scene_changes or duration - scene_changes[-1] > 0.5):
|
||||
scene_changes.append(duration)
|
||||
|
||||
logger.info(f"OpenCV detected {len(scene_changes)-1} scene changes in video")
|
||||
return scene_changes
|
||||
|
||||
return self.scene_detector.detect_scenes(file_path, threshold)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to detect scene changes with OpenCV: {e}")
|
||||
# 返回基本的开始和结束时间
|
||||
logger.error(f"Scene detection failed: {e}")
|
||||
# 返回基本的开始和结束时间作为后备
|
||||
try:
|
||||
video_info = self._get_video_info(file_path)
|
||||
duration = video_info.get('duration', 0)
|
||||
return [0.0, duration] if duration > 0 else [0.0]
|
||||
except:
|
||||
return [0.0]
|
||||
|
||||
|
||||
|
||||
def _generate_thumbnail(self, video_path: str, timestamp: float, output_path: str) -> bool:
|
||||
"""生成视频缩略图"""
|
||||
|
||||
150
scripts/install_dependencies.py
Normal file
150
scripts/install_dependencies.py
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
安装Python依赖的脚本
|
||||
包括新添加的PySceneDetect库
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def install_requirements():
|
||||
"""安装requirements.txt中的依赖"""
|
||||
print("🔧 安装Python依赖...")
|
||||
|
||||
# 获取项目根目录
|
||||
project_root = Path(__file__).parent.parent
|
||||
requirements_file = project_root / "python_core" / "requirements.txt"
|
||||
|
||||
if not requirements_file.exists():
|
||||
print(f"❌ requirements.txt 文件不存在: {requirements_file}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 安装依赖
|
||||
print(f"📦 从 {requirements_file} 安装依赖...")
|
||||
subprocess.check_call([
|
||||
sys.executable, '-m', 'pip', 'install', '-r', str(requirements_file)
|
||||
])
|
||||
|
||||
print("✅ 依赖安装成功!")
|
||||
return True
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ 依赖安装失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 安装过程中出现错误: {e}")
|
||||
return False
|
||||
|
||||
def verify_scenedetect():
|
||||
"""验证PySceneDetect是否正确安装"""
|
||||
print("\n🧪 验证PySceneDetect安装...")
|
||||
|
||||
try:
|
||||
# 尝试导入PySceneDetect
|
||||
import scenedetect
|
||||
from scenedetect import VideoManager, SceneManager
|
||||
from scenedetect.detectors import ContentDetector
|
||||
|
||||
print(f"✅ PySceneDetect 导入成功")
|
||||
print(f" 版本: {scenedetect.__version__}")
|
||||
|
||||
# 测试基本功能
|
||||
print("🔍 测试基本功能...")
|
||||
|
||||
# 创建一个简单的测试
|
||||
video_manager = VideoManager([])
|
||||
scene_manager = SceneManager()
|
||||
scene_manager.add_detector(ContentDetector())
|
||||
|
||||
print("✅ PySceneDetect 功能测试通过")
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
print(f"❌ PySceneDetect 导入失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ PySceneDetect 功能测试失败: {e}")
|
||||
return False
|
||||
|
||||
def verify_other_dependencies():
|
||||
"""验证其他关键依赖"""
|
||||
print("\n🔍 验证其他关键依赖...")
|
||||
|
||||
dependencies = [
|
||||
('opencv-python', 'cv2'),
|
||||
('numpy', 'numpy'),
|
||||
('moviepy', 'moviepy'),
|
||||
('ffmpeg-python', 'ffmpeg'),
|
||||
('loguru', 'loguru'),
|
||||
('pydantic', 'pydantic')
|
||||
]
|
||||
|
||||
failed_deps = []
|
||||
|
||||
for package_name, import_name in dependencies:
|
||||
try:
|
||||
__import__(import_name)
|
||||
print(f"✅ {package_name}")
|
||||
except ImportError:
|
||||
print(f"❌ {package_name}")
|
||||
failed_deps.append(package_name)
|
||||
|
||||
if failed_deps:
|
||||
print(f"\n⚠️ 以下依赖导入失败: {', '.join(failed_deps)}")
|
||||
return False
|
||||
else:
|
||||
print("\n✅ 所有关键依赖验证通过")
|
||||
return True
|
||||
|
||||
def show_usage_info():
|
||||
"""显示使用信息"""
|
||||
print("\n📖 PySceneDetect 使用信息:")
|
||||
print("1. PySceneDetect 已添加到 requirements.txt")
|
||||
print("2. 使用 scenedetect[opencv] 版本,包含OpenCV支持")
|
||||
print("3. 在 media_manager.py 中已集成PySceneDetect场景检测")
|
||||
print("4. 如果PySceneDetect不可用,会自动回退到OpenCV方法")
|
||||
|
||||
print("\n🔧 手动安装命令:")
|
||||
print("pip install scenedetect[opencv]")
|
||||
|
||||
print("\n📚 相关文档:")
|
||||
print("- PySceneDetect: https://pyscenedetect.readthedocs.io/")
|
||||
print("- 项目文档: docs/tauri-security-config-guide.md")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🚀 Python依赖安装和验证")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 安装依赖
|
||||
if not install_requirements():
|
||||
print("❌ 依赖安装失败,退出")
|
||||
return 1
|
||||
|
||||
# 2. 验证PySceneDetect
|
||||
scenedetect_ok = verify_scenedetect()
|
||||
|
||||
# 3. 验证其他依赖
|
||||
other_deps_ok = verify_other_dependencies()
|
||||
|
||||
# 4. 显示使用信息
|
||||
show_usage_info()
|
||||
|
||||
# 5. 总结
|
||||
print("\n" + "=" * 50)
|
||||
if scenedetect_ok and other_deps_ok:
|
||||
print("✅ 所有依赖安装和验证成功!")
|
||||
print("\n🎉 现在可以使用改进的场景检测功能了:")
|
||||
print("- 重新导入视频将使用PySceneDetect进行更准确的分镜")
|
||||
print("- 如果PySceneDetect不可用,系统会自动回退到OpenCV方法")
|
||||
return 0
|
||||
else:
|
||||
print("⚠️ 部分依赖存在问题,请检查上述错误信息")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = main()
|
||||
sys.exit(exit_code)
|
||||
31
scripts/install_scenedetect.bat
Normal file
31
scripts/install_scenedetect.bat
Normal file
@@ -0,0 +1,31 @@
|
||||
@echo off
|
||||
echo 🎬 安装PySceneDetect...
|
||||
|
||||
REM 安装PySceneDetect
|
||||
pip install scenedetect[opencv]
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo ✅ PySceneDetect安装成功!
|
||||
|
||||
REM 验证安装
|
||||
echo 🧪 验证安装...
|
||||
python -c "import scenedetect; print(f'PySceneDetect版本: {scenedetect.__version__}')"
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo ✅ PySceneDetect验证成功!
|
||||
echo.
|
||||
echo 🚀 现在可以使用改进的场景检测功能了:
|
||||
echo - 重新导入视频将使用PySceneDetect进行更准确的分镜
|
||||
echo - 如果PySceneDetect不可用,系统会自动回退到OpenCV方法
|
||||
) else (
|
||||
echo ❌ PySceneDetect验证失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
) else (
|
||||
echo ❌ PySceneDetect安装失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
30
scripts/install_scenedetect_simple.sh
Normal file
30
scripts/install_scenedetect_simple.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 简单的PySceneDetect安装脚本
|
||||
|
||||
echo "🎬 安装PySceneDetect..."
|
||||
|
||||
# 安装PySceneDetect
|
||||
pip install scenedetect[opencv]
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ PySceneDetect安装成功!"
|
||||
|
||||
# 验证安装
|
||||
echo "🧪 验证安装..."
|
||||
python -c "import scenedetect; print(f'PySceneDetect版本: {scenedetect.__version__}')"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ PySceneDetect验证成功!"
|
||||
echo ""
|
||||
echo "🚀 现在可以使用改进的场景检测功能了:"
|
||||
echo "- 重新导入视频将使用PySceneDetect进行更准确的分镜"
|
||||
echo "- 如果PySceneDetect不可用,系统会自动回退到OpenCV方法"
|
||||
else
|
||||
echo "❌ PySceneDetect验证失败"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ PySceneDetect安装失败"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user