Skip to content

Transforms

Transformations modify video frames (cutting, resizing, resampling).

TransformationPipeline

Chain multiple transformations together:

from videopython.base import TransformationPipeline, CutSeconds, Resize

pipeline = TransformationPipeline([
    CutSeconds(start=0, end=10),
    Resize(width=1280, height=720),
])
video = pipeline.run(video)

TransformationPipeline

Source code in src/videopython/base/transforms.py
class TransformationPipeline:
    def __init__(self, transformations: list[Transformation] | None):
        """Initializes pipeline."""
        self.transformations = transformations if transformations else []

    def add(self, transformation: Transformation) -> TransformationPipeline:
        """Adds transformation to the pipeline.

        Args:
            transformation: Transformation to add.

        Returns:
            Pipeline with added transformation.
        """
        self.transformations.append(transformation)
        return self

    def run(self, video: Video) -> Video:
        """Applies pipeline to the video.

        Args:
            video: Video to transform.

        Returns:
            Transformed video.
        """
        for transformation in self.transformations:
            video = transformation.apply(video)
        return video

    def __call__(self, video: Video) -> Video:
        return self.run(video)

__init__

__init__(transformations: list[Transformation] | None)

Initializes pipeline.

Source code in src/videopython/base/transforms.py
def __init__(self, transformations: list[Transformation] | None):
    """Initializes pipeline."""
    self.transformations = transformations if transformations else []

add

add(
    transformation: Transformation,
) -> TransformationPipeline

Adds transformation to the pipeline.

Parameters:

Name Type Description Default
transformation Transformation

Transformation to add.

required

Returns:

Type Description
TransformationPipeline

Pipeline with added transformation.

Source code in src/videopython/base/transforms.py
def add(self, transformation: Transformation) -> TransformationPipeline:
    """Adds transformation to the pipeline.

    Args:
        transformation: Transformation to add.

    Returns:
        Pipeline with added transformation.
    """
    self.transformations.append(transformation)
    return self

run

run(video: Video) -> Video

Applies pipeline to the video.

Parameters:

Name Type Description Default
video Video

Video to transform.

required

Returns:

Type Description
Video

Transformed video.

Source code in src/videopython/base/transforms.py
def run(self, video: Video) -> Video:
    """Applies pipeline to the video.

    Args:
        video: Video to transform.

    Returns:
        Transformed video.
    """
    for transformation in self.transformations:
        video = transformation.apply(video)
    return video

Transformation (Base Class)

Transformation

Bases: ABC

Abstract class for transformation on frames of video.

Source code in src/videopython/base/transforms.py
class Transformation(ABC):
    """Abstract class for transformation on frames of video."""

    @abstractmethod
    def apply(self, video: Video) -> Video:
        pass

CutSeconds

CutSeconds

Bases: Transformation

Cuts video to a specific time range in seconds.

Source code in src/videopython/base/transforms.py
class CutSeconds(Transformation):
    """Cuts video to a specific time range in seconds."""

    def __init__(self, start: float | int, end: float | int):
        """Initialize time-based cutter.

        Args:
            start: Start time in seconds.
            end: End time in seconds.
        """
        self.start = start
        self.end = end

    def apply(self, video: Video) -> Video:
        """Apply time-based cut to video.

        Args:
            video: Input video.

        Returns:
            Video cut from start to end seconds.
        """
        video = video[round(self.start * video.fps) : round(self.end * video.fps)]
        return video

__init__

__init__(start: float | int, end: float | int)

Initialize time-based cutter.

Parameters:

Name Type Description Default
start float | int

Start time in seconds.

required
end float | int

End time in seconds.

required
Source code in src/videopython/base/transforms.py
def __init__(self, start: float | int, end: float | int):
    """Initialize time-based cutter.

    Args:
        start: Start time in seconds.
        end: End time in seconds.
    """
    self.start = start
    self.end = end

apply

apply(video: Video) -> Video

Apply time-based cut to video.

Parameters:

Name Type Description Default
video Video

Input video.

required

Returns:

Type Description
Video

Video cut from start to end seconds.

Source code in src/videopython/base/transforms.py
def apply(self, video: Video) -> Video:
    """Apply time-based cut to video.

    Args:
        video: Input video.

    Returns:
        Video cut from start to end seconds.
    """
    video = video[round(self.start * video.fps) : round(self.end * video.fps)]
    return video

CutFrames

CutFrames

Bases: Transformation

Cuts video to a specific frame range.

Source code in src/videopython/base/transforms.py
class CutFrames(Transformation):
    """Cuts video to a specific frame range."""

    def __init__(self, start: int, end: int):
        """Initialize frame cutter.

        Args:
            start: Start frame index (inclusive).
            end: End frame index (exclusive).
        """
        self.start = start
        self.end = end

    def apply(self, video: Video) -> Video:
        """Apply frame cut to video.

        Args:
            video: Input video.

        Returns:
            Video with frames from start to end.
        """
        video = video[self.start : self.end]
        return video

__init__

__init__(start: int, end: int)

Initialize frame cutter.

Parameters:

Name Type Description Default
start int

Start frame index (inclusive).

required
end int

End frame index (exclusive).

required
Source code in src/videopython/base/transforms.py
def __init__(self, start: int, end: int):
    """Initialize frame cutter.

    Args:
        start: Start frame index (inclusive).
        end: End frame index (exclusive).
    """
    self.start = start
    self.end = end

apply

apply(video: Video) -> Video

Apply frame cut to video.

Parameters:

Name Type Description Default
video Video

Input video.

required

Returns:

Type Description
Video

Video with frames from start to end.

Source code in src/videopython/base/transforms.py
def apply(self, video: Video) -> Video:
    """Apply frame cut to video.

    Args:
        video: Input video.

    Returns:
        Video with frames from start to end.
    """
    video = video[self.start : self.end]
    return video

Resize

Resize

Bases: Transformation

Resizes video to specified dimensions, maintaining aspect ratio if only one dimension is provided.

Source code in src/videopython/base/transforms.py
class Resize(Transformation):
    """Resizes video to specified dimensions, maintaining aspect ratio if only one dimension is provided."""

    def __init__(self, width: int | None = None, height: int | None = None):
        """Initialize resizer.

        Args:
            width: Target width in pixels, or None to maintain aspect ratio.
            height: Target height in pixels, or None to maintain aspect ratio.
        """
        self.width = width
        self.height = height
        if width is None and height is None:
            raise ValueError("You must provide either `width` or `height`!")

    def _resize_frame(self, frame: np.ndarray, new_width: int, new_height: int) -> np.ndarray:
        return cv2.resize(
            frame,
            (new_width, new_height),
            interpolation=cv2.INTER_AREA,
        )

    def apply(self, video: Video) -> Video:
        """Resize video frames to target dimensions.

        Args:
            video: Input video.

        Returns:
            Resized video.
        """
        if self.width and self.height:
            new_height = self.height
            new_width = self.width
        elif self.height is None and self.width:
            video_height = video.video_shape[1]
            video_width = video.video_shape[2]
            new_height = round(video_height * (self.width / video_width))
            new_width = self.width
        elif self.width is None and self.height:
            video_height = video.video_shape[1]
            video_width = video.video_shape[2]
            new_width = round(video_width * (self.height / video_height))
            new_height = self.height

        print(f"Resizing video to: {new_width}x{new_height}!")
        with Pool() as pool:
            frames_copy = pool.starmap(
                self._resize_frame,
                [(frame, new_width, new_height) for frame in video.frames],
            )
        video.frames = np.array(frames_copy)
        return video

__init__

__init__(
    width: int | None = None, height: int | None = None
)

Initialize resizer.

Parameters:

Name Type Description Default
width int | None

Target width in pixels, or None to maintain aspect ratio.

None
height int | None

Target height in pixels, or None to maintain aspect ratio.

None
Source code in src/videopython/base/transforms.py
def __init__(self, width: int | None = None, height: int | None = None):
    """Initialize resizer.

    Args:
        width: Target width in pixels, or None to maintain aspect ratio.
        height: Target height in pixels, or None to maintain aspect ratio.
    """
    self.width = width
    self.height = height
    if width is None and height is None:
        raise ValueError("You must provide either `width` or `height`!")

apply

apply(video: Video) -> Video

Resize video frames to target dimensions.

Parameters:

Name Type Description Default
video Video

Input video.

required

Returns:

Type Description
Video

Resized video.

Source code in src/videopython/base/transforms.py
def apply(self, video: Video) -> Video:
    """Resize video frames to target dimensions.

    Args:
        video: Input video.

    Returns:
        Resized video.
    """
    if self.width and self.height:
        new_height = self.height
        new_width = self.width
    elif self.height is None and self.width:
        video_height = video.video_shape[1]
        video_width = video.video_shape[2]
        new_height = round(video_height * (self.width / video_width))
        new_width = self.width
    elif self.width is None and self.height:
        video_height = video.video_shape[1]
        video_width = video.video_shape[2]
        new_width = round(video_width * (self.height / video_height))
        new_height = self.height

    print(f"Resizing video to: {new_width}x{new_height}!")
    with Pool() as pool:
        frames_copy = pool.starmap(
            self._resize_frame,
            [(frame, new_width, new_height) for frame in video.frames],
        )
    video.frames = np.array(frames_copy)
    return video

ResampleFPS

ResampleFPS

Bases: Transformation

Resamples video to a different frame rate, upsampling or downsampling as needed.

Source code in src/videopython/base/transforms.py
class ResampleFPS(Transformation):
    """Resamples video to a different frame rate, upsampling or downsampling as needed."""

    def __init__(self, fps: int | float):
        """Initialize FPS resampler.

        Args:
            fps: Target frames per second.
        """
        self.fps = float(fps)

    def _downsample(self, video: Video) -> Video:
        target_frame_count = int(len(video.frames) * (self.fps / video.fps))
        new_frame_indices = np.round(np.linspace(0, len(video.frames) - 1, target_frame_count)).astype(int)
        video.frames = video.frames[new_frame_indices]
        video.fps = self.fps
        return video

    def _upsample(self, video: Video) -> Video:
        target_frame_count = int(len(video.frames) * (self.fps / video.fps))
        new_frame_indices = np.linspace(0, len(video.frames) - 1, target_frame_count)
        new_frames = []
        for i in tqdm(range(len(new_frame_indices) - 1)):
            # Interpolate between the two nearest frames
            ratio = new_frame_indices[i] % 1
            new_frame = (1 - ratio) * video.frames[int(new_frame_indices[i])] + ratio * video.frames[
                int(np.ceil(new_frame_indices[i]))
            ]
            new_frames.append(new_frame.astype(np.uint8))
        video.frames = np.array(new_frames, dtype=np.uint8)
        video.fps = self.fps
        return video

    def apply(self, video: Video) -> Video:
        """Resample video to target FPS.

        Args:
            video: Input video.

        Returns:
            Video with target frame rate.
        """
        if video.fps == self.fps:
            return video
        elif video.fps > self.fps:
            print(f"Downsampling video from {video.fps} to {self.fps} FPS.")
            video = self._downsample(video)
        else:
            print(f"Upsampling video from {video.fps} to {self.fps} FPS.")
            video = self._upsample(video)
        return video

__init__

__init__(fps: int | float)

Initialize FPS resampler.

Parameters:

Name Type Description Default
fps int | float

Target frames per second.

required
Source code in src/videopython/base/transforms.py
def __init__(self, fps: int | float):
    """Initialize FPS resampler.

    Args:
        fps: Target frames per second.
    """
    self.fps = float(fps)

apply

apply(video: Video) -> Video

Resample video to target FPS.

Parameters:

Name Type Description Default
video Video

Input video.

required

Returns:

Type Description
Video

Video with target frame rate.

Source code in src/videopython/base/transforms.py
def apply(self, video: Video) -> Video:
    """Resample video to target FPS.

    Args:
        video: Input video.

    Returns:
        Video with target frame rate.
    """
    if video.fps == self.fps:
        return video
    elif video.fps > self.fps:
        print(f"Downsampling video from {video.fps} to {self.fps} FPS.")
        video = self._downsample(video)
    else:
        print(f"Upsampling video from {video.fps} to {self.fps} FPS.")
        video = self._upsample(video)
    return video

Crop

Crop

Bases: Transformation

Crops video to specified dimensions.

Source code in src/videopython/base/transforms.py
class Crop(Transformation):
    """Crops video to specified dimensions."""

    def __init__(self, width: int, height: int, mode: CropMode = CropMode.CENTER):
        """Initialize cropper.

        Args:
            width: Target crop width in pixels.
            height: Target crop height in pixels.
            mode: Crop mode, defaults to center crop.
        """
        self.width = width
        self.height = height
        self.mode = mode

    def apply(self, video: Video) -> Video:
        """Crop video to target dimensions.

        Args:
            video: Input video.

        Returns:
            Cropped video.
        """
        if self.mode == CropMode.CENTER:
            current_shape = video.frame_shape[:2]
            center_height = current_shape[0] // 2
            center_width = current_shape[1] // 2
            width_offset = self.width // 2
            height_offset = self.height // 2
            video.frames = video.frames[
                :,
                center_height - height_offset : center_height + height_offset,
                center_width - width_offset : center_width + width_offset,
                :,
            ]
        else:
            raise ValueError(f"Unknown mode: {self.mode}")
        return video

__init__

__init__(
    width: int,
    height: int,
    mode: CropMode = CropMode.CENTER,
)

Initialize cropper.

Parameters:

Name Type Description Default
width int

Target crop width in pixels.

required
height int

Target crop height in pixels.

required
mode CropMode

Crop mode, defaults to center crop.

CENTER
Source code in src/videopython/base/transforms.py
def __init__(self, width: int, height: int, mode: CropMode = CropMode.CENTER):
    """Initialize cropper.

    Args:
        width: Target crop width in pixels.
        height: Target crop height in pixels.
        mode: Crop mode, defaults to center crop.
    """
    self.width = width
    self.height = height
    self.mode = mode

apply

apply(video: Video) -> Video

Crop video to target dimensions.

Parameters:

Name Type Description Default
video Video

Input video.

required

Returns:

Type Description
Video

Cropped video.

Source code in src/videopython/base/transforms.py
def apply(self, video: Video) -> Video:
    """Crop video to target dimensions.

    Args:
        video: Input video.

    Returns:
        Cropped video.
    """
    if self.mode == CropMode.CENTER:
        current_shape = video.frame_shape[:2]
        center_height = current_shape[0] // 2
        center_width = current_shape[1] // 2
        width_offset = self.width // 2
        height_offset = self.height // 2
        video.frames = video.frames[
            :,
            center_height - height_offset : center_height + height_offset,
            center_width - width_offset : center_width + width_offset,
            :,
        ]
    else:
        raise ValueError(f"Unknown mode: {self.mode}")
    return video

CropMode

CropMode

Bases: Enum

Source code in src/videopython/base/transforms.py
class CropMode(Enum):
    CENTER = "center"