first commit
This commit is contained in:
18
python_core/services/__init__.py
Normal file
18
python_core/services/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Services Module
|
||||
服务层模块
|
||||
|
||||
Provides background services, file management, and task queue functionality.
|
||||
"""
|
||||
|
||||
from .file_manager import FileManager
|
||||
from .task_queue import TaskQueue
|
||||
from .project_manager import ProjectManager
|
||||
from .cache_manager import CacheManager
|
||||
|
||||
__all__ = [
|
||||
"FileManager",
|
||||
"TaskQueue",
|
||||
"ProjectManager",
|
||||
"CacheManager"
|
||||
]
|
||||
383
python_core/services/file_manager.py
Normal file
383
python_core/services/file_manager.py
Normal file
@@ -0,0 +1,383 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
File Manager Service
|
||||
文件管理服务
|
||||
|
||||
Handles file operations, media import, and asset management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
import hashlib
|
||||
import mimetypes
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from config import settings
|
||||
from utils import setup_logger, validate_video_file, validate_audio_file, validate_image_file, get_file_info
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
class FileManager:
|
||||
"""File management service."""
|
||||
|
||||
def __init__(self):
|
||||
self.temp_dir = settings.temp_dir
|
||||
self.cache_dir = settings.cache_dir
|
||||
self.projects_dir = settings.projects_dir
|
||||
|
||||
def import_file(self, source_path: str, project_path: str, copy_file: bool = True) -> Dict[str, Any]:
|
||||
"""
|
||||
Import a media file into a project.
|
||||
|
||||
Args:
|
||||
source_path: Source file path
|
||||
project_path: Project directory path
|
||||
copy_file: Whether to copy file to project assets
|
||||
|
||||
Returns:
|
||||
Dictionary with import result
|
||||
"""
|
||||
try:
|
||||
source = Path(source_path)
|
||||
if not source.exists():
|
||||
raise FileNotFoundError(f"Source file not found: {source_path}")
|
||||
|
||||
# Validate file type
|
||||
file_type = self._get_file_type(source_path)
|
||||
if file_type == "unknown":
|
||||
raise ValueError(f"Unsupported file type: {source.suffix}")
|
||||
|
||||
# Get file info
|
||||
file_info = get_file_info(source_path)
|
||||
if "error" in file_info:
|
||||
raise ValueError(f"Failed to get file info: {file_info['error']}")
|
||||
|
||||
# Determine destination
|
||||
project_dir = Path(project_path)
|
||||
assets_dir = project_dir / "assets"
|
||||
assets_dir.mkdir(exist_ok=True)
|
||||
|
||||
if copy_file:
|
||||
# Copy file to project assets
|
||||
dest_path = assets_dir / source.name
|
||||
|
||||
# Handle name conflicts
|
||||
counter = 1
|
||||
while dest_path.exists():
|
||||
stem = source.stem
|
||||
suffix = source.suffix
|
||||
dest_path = assets_dir / f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
|
||||
shutil.copy2(source, dest_path)
|
||||
final_path = str(dest_path)
|
||||
logger.info(f"Copied file to: {final_path}")
|
||||
else:
|
||||
# Use original path
|
||||
final_path = source_path
|
||||
logger.info(f"Linked file: {final_path}")
|
||||
|
||||
# Generate file hash for deduplication
|
||||
file_hash = self._calculate_file_hash(final_path)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"file_path": final_path,
|
||||
"file_type": file_type,
|
||||
"file_info": file_info,
|
||||
"file_hash": file_hash,
|
||||
"imported_at": file_info.get("modified", 0)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to import file: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def export_file(self, source_path: str, dest_path: str,
|
||||
move_file: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Export a file from project to external location.
|
||||
|
||||
Args:
|
||||
source_path: Source file path
|
||||
dest_path: Destination file path
|
||||
move_file: Whether to move instead of copy
|
||||
|
||||
Returns:
|
||||
Dictionary with export result
|
||||
"""
|
||||
try:
|
||||
source = Path(source_path)
|
||||
dest = Path(dest_path)
|
||||
|
||||
if not source.exists():
|
||||
raise FileNotFoundError(f"Source file not found: {source_path}")
|
||||
|
||||
# Ensure destination directory exists
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Handle name conflicts
|
||||
if dest.exists():
|
||||
counter = 1
|
||||
while dest.exists():
|
||||
stem = dest.stem
|
||||
suffix = dest.suffix
|
||||
dest = dest.parent / f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
|
||||
if move_file:
|
||||
shutil.move(source, dest)
|
||||
logger.info(f"Moved file to: {dest}")
|
||||
else:
|
||||
shutil.copy2(source, dest)
|
||||
logger.info(f"Copied file to: {dest}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"dest_path": str(dest),
|
||||
"operation": "move" if move_file else "copy"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to export file: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def list_project_assets(self, project_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
List all assets in a project.
|
||||
|
||||
Args:
|
||||
project_path: Project directory path
|
||||
|
||||
Returns:
|
||||
Dictionary with asset list
|
||||
"""
|
||||
try:
|
||||
project_dir = Path(project_path)
|
||||
assets_dir = project_dir / "assets"
|
||||
|
||||
if not assets_dir.exists():
|
||||
return {
|
||||
"status": "success",
|
||||
"assets": [],
|
||||
"count": 0
|
||||
}
|
||||
|
||||
assets = []
|
||||
for file_path in assets_dir.rglob("*"):
|
||||
if file_path.is_file():
|
||||
file_type = self._get_file_type(str(file_path))
|
||||
if file_type != "unknown":
|
||||
file_info = get_file_info(str(file_path))
|
||||
|
||||
assets.append({
|
||||
"name": file_path.name,
|
||||
"path": str(file_path),
|
||||
"relative_path": str(file_path.relative_to(project_dir)),
|
||||
"type": file_type,
|
||||
"size": file_info.get("size", 0),
|
||||
"size_mb": file_info.get("size_mb", 0),
|
||||
"modified": file_info.get("modified", 0),
|
||||
"duration": file_info.get("duration"),
|
||||
"width": file_info.get("width"),
|
||||
"height": file_info.get("height"),
|
||||
"file_hash": self._calculate_file_hash(str(file_path))
|
||||
})
|
||||
|
||||
# Sort by name
|
||||
assets.sort(key=lambda x: x["name"].lower())
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"assets": assets,
|
||||
"count": len(assets)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list project assets: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def create_temp_file(self, suffix: str = "", prefix: str = "mixvideo_") -> str:
|
||||
"""
|
||||
Create a temporary file.
|
||||
|
||||
Args:
|
||||
suffix: File suffix/extension
|
||||
prefix: File prefix
|
||||
|
||||
Returns:
|
||||
Path to temporary file
|
||||
"""
|
||||
import tempfile
|
||||
|
||||
fd, temp_path = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=self.temp_dir)
|
||||
os.close(fd) # Close file descriptor
|
||||
|
||||
logger.debug(f"Created temp file: {temp_path}")
|
||||
return temp_path
|
||||
|
||||
def cleanup_temp_files(self, max_age_hours: int = 24) -> Dict[str, Any]:
|
||||
"""
|
||||
Clean up old temporary files.
|
||||
|
||||
Args:
|
||||
max_age_hours: Maximum age of files to keep
|
||||
|
||||
Returns:
|
||||
Dictionary with cleanup result
|
||||
"""
|
||||
try:
|
||||
from utils import clean_temp_files
|
||||
|
||||
clean_temp_files(self.temp_dir, max_age_hours)
|
||||
clean_temp_files(self.cache_dir, max_age_hours)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Cleaned temp files older than {max_age_hours} hours"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to cleanup temp files: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def get_disk_usage(self, path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get disk usage information for a path.
|
||||
|
||||
Args:
|
||||
path: Directory path
|
||||
|
||||
Returns:
|
||||
Dictionary with disk usage info
|
||||
"""
|
||||
try:
|
||||
path_obj = Path(path)
|
||||
if not path_obj.exists():
|
||||
raise FileNotFoundError(f"Path not found: {path}")
|
||||
|
||||
# Get disk usage
|
||||
usage = shutil.disk_usage(path)
|
||||
|
||||
# Calculate directory size if it's a directory
|
||||
if path_obj.is_dir():
|
||||
dir_size = sum(f.stat().st_size for f in path_obj.rglob('*') if f.is_file())
|
||||
else:
|
||||
dir_size = path_obj.stat().st_size
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"path": path,
|
||||
"total_space": usage.total,
|
||||
"used_space": usage.used,
|
||||
"free_space": usage.free,
|
||||
"directory_size": dir_size,
|
||||
"usage_percent": (usage.used / usage.total) * 100
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get disk usage: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def _get_file_type(self, file_path: str) -> str:
|
||||
"""
|
||||
Determine file type based on extension and validation.
|
||||
|
||||
Args:
|
||||
file_path: Path to file
|
||||
|
||||
Returns:
|
||||
File type: 'video', 'audio', 'image', or 'unknown'
|
||||
"""
|
||||
if validate_video_file(file_path):
|
||||
return "video"
|
||||
elif validate_audio_file(file_path):
|
||||
return "audio"
|
||||
elif validate_image_file(file_path):
|
||||
return "image"
|
||||
else:
|
||||
return "unknown"
|
||||
|
||||
def _calculate_file_hash(self, file_path: str) -> str:
|
||||
"""
|
||||
Calculate MD5 hash of a file.
|
||||
|
||||
Args:
|
||||
file_path: Path to file
|
||||
|
||||
Returns:
|
||||
MD5 hash string
|
||||
"""
|
||||
try:
|
||||
hash_md5 = hashlib.md5()
|
||||
with open(file_path, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_md5.update(chunk)
|
||||
return hash_md5.hexdigest()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def find_duplicates(self, project_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Find duplicate files in a project based on hash.
|
||||
|
||||
Args:
|
||||
project_path: Project directory path
|
||||
|
||||
Returns:
|
||||
Dictionary with duplicate files
|
||||
"""
|
||||
try:
|
||||
assets_result = self.list_project_assets(project_path)
|
||||
if assets_result["status"] != "success":
|
||||
return assets_result
|
||||
|
||||
# Group files by hash
|
||||
hash_groups = {}
|
||||
for asset in assets_result["assets"]:
|
||||
file_hash = asset["file_hash"]
|
||||
if file_hash:
|
||||
if file_hash not in hash_groups:
|
||||
hash_groups[file_hash] = []
|
||||
hash_groups[file_hash].append(asset)
|
||||
|
||||
# Find duplicates (groups with more than one file)
|
||||
duplicates = {hash_val: files for hash_val, files in hash_groups.items() if len(files) > 1}
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"duplicates": duplicates,
|
||||
"duplicate_count": sum(len(files) - 1 for files in duplicates.values()),
|
||||
"total_duplicate_size": sum(
|
||||
sum(file["size"] for file in files[1:]) # Skip first file in each group
|
||||
for files in duplicates.values()
|
||||
)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to find duplicates: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
427
python_core/services/project_manager.py
Normal file
427
python_core/services/project_manager.py
Normal file
@@ -0,0 +1,427 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Project Manager Service
|
||||
项目管理服务
|
||||
|
||||
Handles project creation, loading, saving, and management.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from config import settings
|
||||
from utils import setup_logger, ensure_directory, get_unique_filename
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
class ProjectManager:
|
||||
"""Project management service."""
|
||||
|
||||
def __init__(self):
|
||||
self.projects_dir = settings.projects_dir
|
||||
self.temp_dir = settings.temp_dir
|
||||
|
||||
def create_project(self, name: str, description: str = "") -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new project.
|
||||
|
||||
Args:
|
||||
name: Project name
|
||||
description: Project description
|
||||
|
||||
Returns:
|
||||
Dictionary with project information
|
||||
"""
|
||||
try:
|
||||
# Create project directory
|
||||
project_path = self.projects_dir / name
|
||||
ensure_directory(project_path)
|
||||
|
||||
# Create project structure
|
||||
(project_path / "assets").mkdir(exist_ok=True)
|
||||
(project_path / "exports").mkdir(exist_ok=True)
|
||||
(project_path / "temp").mkdir(exist_ok=True)
|
||||
|
||||
# Create project info
|
||||
project_info = {
|
||||
"id": str(uuid.uuid4()),
|
||||
"name": name,
|
||||
"description": description,
|
||||
"path": str(project_path),
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"modified_at": datetime.now().isoformat(),
|
||||
"version": "1.0",
|
||||
"settings": {
|
||||
"resolution": settings.max_video_resolution,
|
||||
"fps": settings.default_fps,
|
||||
"video_codec": settings.default_video_codec,
|
||||
"audio_codec": settings.default_audio_codec
|
||||
},
|
||||
"video_tracks": [],
|
||||
"audio_tracks": [],
|
||||
"timeline": {
|
||||
"duration": 0.0,
|
||||
"zoom": 1.0,
|
||||
"position": 0.0
|
||||
}
|
||||
}
|
||||
|
||||
# Save project file
|
||||
project_file = project_path / f"{name}.mixvideo"
|
||||
with open(project_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(project_info, f, indent=2, ensure_ascii=False)
|
||||
|
||||
logger.info(f"Created project: {name} at {project_path}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"project": project_info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create project: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def load_project(self, project_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Load an existing project.
|
||||
|
||||
Args:
|
||||
project_path: Path to project file or directory
|
||||
|
||||
Returns:
|
||||
Dictionary with project information
|
||||
"""
|
||||
try:
|
||||
path = Path(project_path)
|
||||
|
||||
# If it's a directory, look for .mixvideo file
|
||||
if path.is_dir():
|
||||
mixvideo_files = list(path.glob("*.mixvideo"))
|
||||
if not mixvideo_files:
|
||||
raise FileNotFoundError(f"No .mixvideo file found in {path}")
|
||||
project_file = mixvideo_files[0]
|
||||
else:
|
||||
project_file = path
|
||||
|
||||
if not project_file.exists():
|
||||
raise FileNotFoundError(f"Project file not found: {project_file}")
|
||||
|
||||
# Load project data
|
||||
with open(project_file, 'r', encoding='utf-8') as f:
|
||||
project_info = json.load(f)
|
||||
|
||||
# Update modified time
|
||||
project_info["modified_at"] = datetime.now().isoformat()
|
||||
|
||||
logger.info(f"Loaded project: {project_info['name']}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"project": project_info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load project: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def save_project(self, project_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Save project to file.
|
||||
|
||||
Args:
|
||||
project_info: Project information dictionary
|
||||
|
||||
Returns:
|
||||
Dictionary with save result
|
||||
"""
|
||||
try:
|
||||
project_path = Path(project_info["path"])
|
||||
project_name = project_info["name"]
|
||||
|
||||
# Update modified time
|
||||
project_info["modified_at"] = datetime.now().isoformat()
|
||||
|
||||
# Save project file
|
||||
project_file = project_path / f"{project_name}.mixvideo"
|
||||
with open(project_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(project_info, f, indent=2, ensure_ascii=False)
|
||||
|
||||
logger.info(f"Saved project: {project_name}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Project saved to {project_file}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save project: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def list_projects(self) -> Dict[str, Any]:
|
||||
"""
|
||||
List all projects in the projects directory.
|
||||
|
||||
Returns:
|
||||
Dictionary with list of projects
|
||||
"""
|
||||
try:
|
||||
projects = []
|
||||
|
||||
for project_file in self.projects_dir.glob("**/*.mixvideo"):
|
||||
try:
|
||||
with open(project_file, 'r', encoding='utf-8') as f:
|
||||
project_info = json.load(f)
|
||||
|
||||
# Add basic project info
|
||||
projects.append({
|
||||
"id": project_info.get("id"),
|
||||
"name": project_info.get("name"),
|
||||
"description": project_info.get("description", ""),
|
||||
"path": str(project_file.parent),
|
||||
"created_at": project_info.get("created_at"),
|
||||
"modified_at": project_info.get("modified_at"),
|
||||
"file_size": project_file.stat().st_size
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read project file {project_file}: {str(e)}")
|
||||
continue
|
||||
|
||||
# Sort by modified time (newest first)
|
||||
projects.sort(key=lambda x: x.get("modified_at", ""), reverse=True)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"projects": projects,
|
||||
"count": len(projects)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to list projects: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def delete_project(self, project_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete a project.
|
||||
|
||||
Args:
|
||||
project_path: Path to project directory
|
||||
|
||||
Returns:
|
||||
Dictionary with deletion result
|
||||
"""
|
||||
try:
|
||||
import shutil
|
||||
|
||||
path = Path(project_path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Project path not found: {path}")
|
||||
|
||||
if path.is_dir():
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
path.unlink()
|
||||
|
||||
logger.info(f"Deleted project: {path}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Project deleted: {path}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete project: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def add_video_track(self, project_info: Dict[str, Any], file_path: str,
|
||||
start_time: float = 0.0, duration: Optional[float] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Add a video track to the project.
|
||||
|
||||
Args:
|
||||
project_info: Project information
|
||||
file_path: Path to video file
|
||||
start_time: Start time in timeline
|
||||
duration: Duration (auto-detect if None)
|
||||
|
||||
Returns:
|
||||
Updated project information
|
||||
"""
|
||||
try:
|
||||
from utils import get_file_info
|
||||
|
||||
file_info = get_file_info(file_path)
|
||||
if "error" in file_info:
|
||||
raise ValueError(f"Invalid video file: {file_info['error']}")
|
||||
|
||||
track_id = str(uuid.uuid4())
|
||||
video_track = {
|
||||
"id": track_id,
|
||||
"name": Path(file_path).stem,
|
||||
"file_path": file_path,
|
||||
"start_time": start_time,
|
||||
"duration": duration or file_info.get("duration", 0.0),
|
||||
"file_info": file_info,
|
||||
"effects": [],
|
||||
"volume": 1.0,
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
project_info["video_tracks"].append(video_track)
|
||||
project_info["modified_at"] = datetime.now().isoformat()
|
||||
|
||||
logger.info(f"Added video track: {video_track['name']}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"project": project_info,
|
||||
"track_id": track_id
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add video track: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def add_audio_track(self, project_info: Dict[str, Any], file_path: str,
|
||||
start_time: float = 0.0, duration: Optional[float] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Add an audio track to the project.
|
||||
|
||||
Args:
|
||||
project_info: Project information
|
||||
file_path: Path to audio file
|
||||
start_time: Start time in timeline
|
||||
duration: Duration (auto-detect if None)
|
||||
|
||||
Returns:
|
||||
Updated project information
|
||||
"""
|
||||
try:
|
||||
from utils import get_file_info
|
||||
|
||||
file_info = get_file_info(file_path)
|
||||
if "error" in file_info:
|
||||
raise ValueError(f"Invalid audio file: {file_info['error']}")
|
||||
|
||||
track_id = str(uuid.uuid4())
|
||||
audio_track = {
|
||||
"id": track_id,
|
||||
"name": Path(file_path).stem,
|
||||
"file_path": file_path,
|
||||
"start_time": start_time,
|
||||
"duration": duration or file_info.get("duration", 0.0),
|
||||
"file_info": file_info,
|
||||
"effects": [],
|
||||
"volume": 1.0,
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
project_info["audio_tracks"].append(audio_track)
|
||||
project_info["modified_at"] = datetime.now().isoformat()
|
||||
|
||||
logger.info(f"Added audio track: {audio_track['name']}")
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"project": project_info,
|
||||
"track_id": track_id
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add audio track: {str(e)}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Command line interface for project management."""
|
||||
parser = argparse.ArgumentParser(description="Project Manager")
|
||||
parser.add_argument("--action", required=True,
|
||||
choices=["create", "load", "save", "list", "delete", "get_info"],
|
||||
help="Action to perform")
|
||||
parser.add_argument("--name", help="Project name")
|
||||
parser.add_argument("--path", help="Project path")
|
||||
parser.add_argument("--data", help="Project data as JSON string")
|
||||
parser.add_argument("--description", help="Project description")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
manager = ProjectManager()
|
||||
|
||||
if args.action == "create":
|
||||
if not args.name:
|
||||
raise ValueError("Project name is required for create action")
|
||||
result = manager.create_project(args.name, args.description or "")
|
||||
|
||||
elif args.action == "load":
|
||||
if not args.path:
|
||||
raise ValueError("Project path is required for load action")
|
||||
result = manager.load_project(args.path)
|
||||
|
||||
elif args.action == "save":
|
||||
if not args.data:
|
||||
raise ValueError("Project data is required for save action")
|
||||
project_data = json.loads(args.data)
|
||||
result = manager.save_project(project_data)
|
||||
|
||||
elif args.action == "list":
|
||||
result = manager.list_projects()
|
||||
|
||||
elif args.action == "delete":
|
||||
if not args.path:
|
||||
raise ValueError("Project path is required for delete action")
|
||||
result = manager.delete_project(args.path)
|
||||
|
||||
elif args.action == "get_info":
|
||||
if not args.path:
|
||||
raise ValueError("Project path is required for get_info action")
|
||||
result = manager.load_project(args.path)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown action: {args.action}")
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user