video / app /video_processor.py
ankitverma304's picture
Upload 15 files
039285a verified
Raw History Blame Contribute Delete
3.79 kB
"""Video cropping and clip concatenation using MoviePy."""
import logging
import traceback
from datetime import datetime
from pathlib import Path
# Support both moviepy 1.x (moviepy.editor) and 2.x (moviepy)
try:
from moviepy import VideoFileClip, concatenate_videoclips # 2.x
except ImportError:
from moviepy.editor import VideoFileClip, concatenate_videoclips # 1.x
from app.utils import format_duration
from config.settings import settings
logger = logging.getLogger("media_intelligence.video_processor")
def _subclip(clip, start: float, end: float):
"""Call the correct subclip method for moviepy 1.x and 2.x."""
if hasattr(clip, "subclipped"):
return clip.subclipped(start, end) # moviepy 2.x
return clip.subclip(start, end) # moviepy 1.x
def crop_video(video_path: str, segments: list) -> tuple:
"""
Extract and concatenate the requested time segments from *video_path*.
Args:
video_path: Path to the source video file.
segments: List of dicts with 'start' and 'end' keys (seconds).
Returns:
(output_path, duration_str)
Raises:
ValueError: No valid segments provided.
RuntimeError: MoviePy processing failure.
"""
if not segments:
raise ValueError(
"No segments provided for cropping. "
"Please select at least one segment and try again."
)
video_path = Path(video_path)
logger.info(f"Crop start: {video_path.name} — {len(segments)} segment(s)")
output_dir = Path(settings.OUTPUT_DIR)
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = output_dir / f"short_video_{timestamp}.mp4"
source = None
clips = []
try:
source = VideoFileClip(str(video_path))
total_duration = source.duration
logger.info(f"Source duration: {format_duration(total_duration)}")
for seg in segments:
start = float(seg["start"])
end = float(seg["end"])
start = max(0.0, min(start, total_duration))
end = max(0.0, min(end, total_duration))
if start >= end:
logger.warning(f"Skipping invalid segment: {start:.1f}s – {end:.1f}s")
continue
logger.info(f"Extracting: {start:.1f}s → {end:.1f}s")
clips.append(_subclip(source, start, end))
if not clips:
raise ValueError(
"All selected segments are outside the video duration. "
"Please verify the video and re-analyse."
)
logger.info(f"Joining {len(clips)} clip(s)…")
final = concatenate_videoclips(clips)
duration_str = format_duration(final.duration)
logger.info(f"Writing: {output_path}")
final.write_videofile(
str(output_path),
codec="libx264",
audio_codec="aac",
verbose=False,
logger=None,
)
final.close()
logger.info(f"Crop complete: {output_path.name} ({duration_str})")
return str(output_path), duration_str
except (ValueError, RuntimeError):
raise
except Exception as exc:
msg = (
f"Video processing failed. "
f"Possible reason: corrupt file or unsupported codec. "
f"Detail: {exc}"
)
logger.error(f"{msg}\n{traceback.format_exc()}")
raise RuntimeError(msg) from exc
finally:
for clip in clips:
try:
clip.close()
except Exception:
pass
if source is not None:
try:
source.close()
except Exception:
pass