"""
Frame class for FMUS-VID.
This module provides the Frame class for handling individual video frames.
"""
from typing import Union, Tuple, Optional, Any
from pathlib import Path
import logging
logger = logging.getLogger(__name__)
[docs]
class Frame:
"""
Class for handling individual video frames.
This class provides methods for manipulating and saving frames.
"""
[docs]
def __init__(self, frame_data: Any, backend: Any):
"""
Initialize a Frame object.
Args:
frame_data: Backend-specific frame data
backend: Backend instance
"""
self._frame_data = frame_data
self._backend = backend
[docs]
def save(self, path: Union[str, Path], **kwargs) -> None:
"""
Save frame to file.
Args:
path: Output file path
**kwargs: Save options
Example:
>>> frame.save("thumbnail.jpg", quality=95)
"""
self._backend.save_frame(self._frame_data, path, **kwargs)
[docs]
def resize(self, width: Optional[int] = None, height: Optional[int] = None,
keep_aspect: bool = True) -> 'Frame':
"""
Resize frame to specified dimensions.
Args:
width: Target width (None to auto-calculate from height)
height: Target height (None to auto-calculate from width)
keep_aspect: Maintain aspect ratio if only one dimension is specified
Returns:
New Frame object
Example:
>>> thumbnail = frame.resize(width=320)
"""
new_frame_data = self._backend.resize_frame(
self._frame_data,
width=width,
height=height,
keep_aspect=keep_aspect
)
return Frame(new_frame_data, self._backend)
[docs]
def crop(self, x: int, y: int, width: int, height: int) -> 'Frame':
"""
Crop frame to specified region.
Args:
x: X coordinate of top-left corner
y: Y coordinate of top-left corner
width: Width of crop region
height: Height of crop region
Returns:
New Frame object
Example:
>>> cropped = frame.crop(100, 100, 400, 300)
"""
new_frame_data = self._backend.crop_frame(
self._frame_data,
x=x,
y=y,
width=width,
height=height
)
return Frame(new_frame_data, self._backend)
[docs]
def grayscale(self) -> 'Frame':
"""
Convert frame to grayscale.
Returns:
New Frame object
Example:
>>> bw = frame.grayscale()
"""
new_frame_data = self._backend.grayscale_frame(self._frame_data)
return Frame(new_frame_data, self._backend)
[docs]
def blur(self, radius: float) -> 'Frame':
"""
Apply Gaussian blur with specified radius.
Args:
radius: Blur radius
Returns:
New Frame object
Example:
>>> blurred = frame.blur(5)
"""
new_frame_data = self._backend.blur_frame(self._frame_data, radius=radius)
return Frame(new_frame_data, self._backend)
[docs]
def brightness(self, factor: float) -> 'Frame':
"""
Adjust frame brightness.
Args:
factor: Brightness factor (1.0 = original, 1.5 = +50%, 0.5 = -50%)
Returns:
New Frame object
Example:
>>> brighter = frame.brightness(1.2)
"""
new_frame_data = self._backend.brightness_frame(self._frame_data, factor=factor)
return Frame(new_frame_data, self._backend)
[docs]
def contrast(self, factor: float) -> 'Frame':
"""
Adjust frame contrast.
Args:
factor: Contrast factor (1.0 = original, 1.5 = +50%, 0.5 = -50%)
Returns:
New Frame object
Example:
>>> higher_contrast = frame.contrast(1.2)
"""
new_frame_data = self._backend.contrast_frame(self._frame_data, factor=factor)
return Frame(new_frame_data, self._backend)
[docs]
def overlay(self, image, position: Tuple[int, int] = (0, 0),
opacity: float = 1.0) -> 'Frame':
"""
Overlay an image on the frame.
Args:
image: Image to overlay (Frame or PIL Image)
position: (x, y) coordinates for placement
opacity: Opacity of overlay (1.0 = fully opaque)
Returns:
New Frame object
Example:
>>> with_logo = frame.overlay(logo, position=(20, 20), opacity=0.8)
"""
# If image is a Frame, extract its data
if isinstance(image, Frame):
image = image._frame_data
new_frame_data = self._backend.overlay_frame(
self._frame_data,
image,
position=position,
opacity=opacity
)
return Frame(new_frame_data, self._backend)
[docs]
def add_text(self, text: str, position: Tuple[int, int],
font: str = "Arial", size: int = 24,
color: Union[str, Tuple[int, int, int]] = "white") -> 'Frame':
"""
Add text to the frame.
Args:
text: Text to add
position: (x, y) coordinates for placement
font: Font name
size: Font size
color: Text color (name or RGB tuple)
Returns:
New Frame object
Example:
>>> with_caption = frame.add_text("Hello World", position=(50, 50), size=32)
"""
new_frame_data = self._backend.add_text_to_frame(
self._frame_data,
text=text,
position=position,
font=font,
size=size,
color=color
)
return Frame(new_frame_data, self._backend)
[docs]
def to_pil(self):
"""
Convert frame to PIL Image.
Returns:
PIL Image object
Example:
>>> pil_image = frame.to_pil()
>>> pil_image.show()
"""
return self._backend.frame_to_pil(self._frame_data)
[docs]
def to_numpy(self):
"""
Convert frame to NumPy array.
Returns:
NumPy array (height, width, channels)
Example:
>>> import numpy as np
>>> array = frame.to_numpy()
>>> print(f"Shape: {array.shape}")
"""
return self._backend.frame_to_numpy(self._frame_data)
[docs]
def __repr__(self) -> str:
"""String representation of the Frame object."""
try:
# Try to get frame dimensions
width, height = self._backend.get_frame_dimensions(self._frame_data)
return f"Frame({width}x{height})"
except Exception:
return "Frame(unknown dimensions)"