Spaces:
Running
Running
"""Video processing utilities for the DRS application. | |
This module provides helper functions to save uploaded videos to the | |
filesystem and to trim the last N seconds from a video. Using | |
OpenCV's ``VideoCapture`` and ``VideoWriter`` avoids external | |
dependencies like ffmpeg or moviepy, which may not be installed in | |
all execution environments. | |
""" | |
from __future__ import annotations | |
import os | |
import shutil | |
from pathlib import Path | |
from typing import Union | |
import cv2 | |
def save_uploaded_video(name: str, file_obj: Union[bytes, str, Path]) -> str: | |
"""Persist an uploaded video to a predictable location on disk. | |
When a user records or uploads a video in the Gradio interface, it | |
arrives as a temporary file object. To analyse the video later we | |
copy it into the working directory using its original filename. | |
Parameters | |
---------- | |
name: str | |
The original filename from the upload widget. | |
file_obj: Union[bytes, str, Path] | |
The file-like object representing the uploaded video. Gradio | |
passes the file as a ``gradio.Files`` object whose `.name` | |
property holds the temporary path. This function accepts | |
either the temporary path or an open file handle. | |
Returns | |
------- | |
str | |
The absolute path where the video has been saved. | |
""" | |
# Determine a safe output directory. Use the current working | |
# directory so that Gradio can later access the file by path. | |
output_dir = Path(os.getcwd()) / "user_videos" | |
output_dir.mkdir(exist_ok=True) | |
# Compose an output filename; avoid overwriting by prefixing with an | |
# incrementing integer if necessary. | |
base_name = Path(name).stem | |
ext = Path(name).suffix or ".mp4" | |
counter = 0 | |
dest = output_dir / f"{base_name}{ext}" | |
while dest.exists(): | |
counter += 1 | |
dest = output_dir / f"{base_name}_{counter}{ext}" | |
# If file_obj is a path, simply copy it; otherwise, read and write | |
if isinstance(file_obj, (str, Path)): | |
shutil.copy(str(file_obj), dest) | |
else: | |
# Gradio passes a file-like object with a `.read()` method | |
with open(dest, "wb") as f_out: | |
f_out.write(file_obj.read()) | |
return str(dest) | |
def trim_last_seconds(input_path: str, output_path: str, seconds: int) -> None: | |
"""Save the last ``seconds`` of a video to ``output_path``. | |
This function reads the entire video file, calculates the starting | |
frame corresponding to ``seconds`` before the end, and writes the | |
remaining frames to a new video using OpenCV. If the video is | |
shorter than the requested duration, the whole video is copied. | |
Parameters | |
---------- | |
input_path: str | |
Path to the source video file. | |
output_path: str | |
Path where the trimmed video will be saved. | |
seconds: int | |
The duration from the end of the video to retain. | |
""" | |
cap = cv2.VideoCapture(input_path) | |
if not cap.isOpened(): | |
raise RuntimeError(f"Unable to open video: {input_path}") | |
fps = cap.get(cv2.CAP_PROP_FPS) | |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
if fps <= 0: | |
fps = 30.0 # default fallback | |
frames_to_keep = int(seconds * fps) | |
start_frame = max(total_frames - frames_to_keep, 0) | |
# Prepare writer with the same properties as the input | |
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
fourcc = cv2.VideoWriter_fourcc(*"mp4v") | |
out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) | |
# Skip frames until start_frame | |
current = 0 | |
while current < start_frame: | |
ret, _ = cap.read() | |
if not ret: | |
break | |
current += 1 | |
# Write remaining frames | |
while True: | |
ret, frame = cap.read() | |
if not ret: | |
break | |
out.write(frame) | |
cap.release() | |
out.release() |