Skip to content

Commit

Permalink
Added Docstring
Browse files Browse the repository at this point in the history
  • Loading branch information
SohamTilekar committed Feb 20, 2024
1 parent 12a9539 commit 2fbb8dc
Showing 1 changed file with 172 additions and 13 deletions.
185 changes: 172 additions & 13 deletions vidiopy/video/VideoFileClip.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Callable, Self, Union
from typing import Self, Union
from PIL import Image
import ffmpegio
import numpy as np
Expand All @@ -8,7 +8,53 @@


class VideoFileClip(VideoClip):
def __init__(self, filename, audio=True, ffmpeg_options=None):
"""
A class used to represent a video file clip.
This class extends the VideoClip class and provides additional functionality for working with video files. It uses ffmpeg to read video files, extract frames, and set the properties of the video clip. It also provides methods for transforming frames, creating sub-clips, and generating frame representations.
Attributes:
filename (str): The name of the video file.
fps (float): The frames per second of the video.
size (tuple): The width and height of the video.
start (float): The start time of the video clip.
end (float): The end time of the video clip.
duration (float): The duration of the video clip.
audio (AudioFileClip): The audio of the video clip.
clip (tuple): The frames of the video clip as PIL Images.
Methods:
fl_frame_transform(func, *args, **kwargs): Applies a function to each frame of the video clip.
fl_clip_transform(func, *args, **kwargs): Applies a function to each frame of the video clip along with its timestamp.
sub_clip(t_start=None, t_end=None): Returns a sub-clip of the video clip.
sub_clip_copy(t_start=None, t_end=None): Returns a copy of a sub-clip of the video clip.
make_frame_array(t): Returns a numpy array representation of a specific frame in the video clip.
make_frame_pil(t): Returns a PIL Image representation of a specific frame in the video clip.
_import_video_clip(file_name, ffmpeg_options=None): Imports a video clip from a file using ffmpeg.
"""

def __init__(
self, filename: str, audio: bool = True, ffmpeg_options: dict | None = None
) -> None:
"""
Initializes a new instance of the VideoFileClip class.
This method creates a new VideoFileClip from a video file. It uses ffmpeg to read the video file, extract the frames, and set the properties of the video clip.
Args:
filename (str): The name of the video file to import.
audio (bool, optional): Whether to include audio in the video clip. Defaults to True.
ffmpeg_options (dict | None, optional): Additional options to pass to ffmpeg. Defaults to None.
Raises:
None
Example:
>>> video_clip = VideoFileClip("video.mp4")
Note:
This method uses ffmpeg to read the video file.
"""
super().__init__()

self.filename = filename
Expand Down Expand Up @@ -48,7 +94,7 @@ def __eq__(self, other) -> bool:
return False

return (
isinstance(other, VideoClip)
isinstance(other, VideoFileClip)
and self.fps == other.fps
and self.size == other.size
and self.start == other.start
Expand All @@ -63,7 +109,32 @@ def __eq__(self, other) -> bool:
#################

@requires_start_end
def fl_frame_transform(self, func, *args, **kwargs):
def fl_frame_transform(self, func, *args, **kwargs) -> Self:
"""
Applies a function to each frame of the video clip.
This method iterates over each frame in the video clip, applies a function to it, and replaces the original frame with the result.
Args:
func (callable): The function to apply to each frame. It should take an Image as its first argument, and return an Image.
*args: Additional positional arguments to pass to func.
**kwargs: Additional keyword arguments to pass to func.
Returns:
Self: Returns the instance of the class with updated frames.
Raises:
None
Example:
>>> video_clip = VideoClip()
>>> def invert_colors(image):
... return ImageOps.invert(image)
>>> video_clip.fl_frame_transform(invert_colors)
Note:
This method requires the start and end of the video clip to be set.
"""
clip: list[Image.Image] = []
for frame in self.clip:
frame: Image.Image = func(frame, *args, **kwargs)
Expand All @@ -72,7 +143,34 @@ def fl_frame_transform(self, func, *args, **kwargs):
return self

@requires_fps
def fl_clip_transform(self, func, *args, **kwargs):
def fl_clip_transform(self, func, *args, **kwargs) -> Self:
"""
Applies a function to each frame of the video clip along with its timestamp.
This method iterates over each frame in the video clip, applies a function to it and its timestamp, and replaces the original frame with the result.
Args:
func (callable): The function to apply to each frame. It should take an Image and a float (representing the timestamp) as its first two arguments, and return an Image.
*args: Additional positional arguments to pass to func.
**kwargs: Additional keyword arguments to pass to func.
Returns:
Self: Returns the instance of the class with updated frames.
Raises:
None
Example:
>>> video_clip = VideoClip()
>>> def add_timestamp(image, timestamp):
... draw = ImageDraw.Draw(image)
... draw.text((10, 10), str(timestamp), fill="white")
... return image
>>> video_clip.fl_clip_transform(add_timestamp)
Note:
This method requires the fps of the video clip to be set.
"""
td = 1 / self.fps
frame_time = 0.0
clip: list[Image.Image] = []
Expand All @@ -83,11 +181,6 @@ def fl_clip_transform(self, func, *args, **kwargs):
self.clip = tuple(clip)
return self

def fx(self, func: Callable[..., Self], *args, **kwargs):
# Apply an effect function directly to the clip
self = func(self, *args, **kwargs)
return self

@requires_fps
def sub_clip(
self,
Expand Down Expand Up @@ -174,7 +267,28 @@ def sub_clip_copy(
return instance

@requires_duration
def make_frame_array(self, t) -> np.ndarray:
def make_frame_array(self, t: int | float) -> np.ndarray:
"""
Generates a numpy array representation of a specific frame in the video clip.
This method calculates the index of the frame for a specific time, retrieves the frame from the video clip, and converts it to a numpy array.
Args:
t (int | float): The time of the frame to convert.
Returns:
np.ndarray: The numpy array representation of the frame.
Raises:
ValueError: If the duration of the video clip is not set.
Example:
>>> video_clip = VideoClip()
>>> frame_array = video_clip.make_frame_array(10)
Note:
This method requires the duration of the video clip to be set.
"""
if self.duration is None:
raise ValueError("Duration is Not Set.")
time_per_frame = self.duration / len(self.clip)
Expand All @@ -183,15 +297,60 @@ def make_frame_array(self, t) -> np.ndarray:
return np.array(self.clip[frame_index])

@requires_duration
def make_frame_pil(self, t) -> Image.Image:
def make_frame_pil(self, t: int | float) -> Image.Image:
"""
Generates a PIL Image representation of a specific frame in the video clip.
This method calculates the index of the frame for a specific time, retrieves the frame from the video clip, and returns it as a PIL Image.
Args:
t (int | float): The time of the frame to convert.
Returns:
Image.Image: The PIL Image representation of the frame.
Raises:
ValueError: If the duration of the video clip is not set.
Example:
>>> video_clip = VideoClip()
>>> frame_image = video_clip.make_frame_pil(10)
Note:
This method requires the duration of the video clip to be set.
"""
if self.duration is None:
raise ValueError("Duration is Not Set.")
time_per_frame = self.duration / len(self.clip)
frame_index = t / time_per_frame
frame_index = int(min(len(self.clip) - 1, max(0, frame_index)))
return self.clip[frame_index]

def _import_video_clip(self, file_name, ffmpeg_options):
def _import_video_clip(
self, file_name: str, ffmpeg_options: dict | None = None
) -> tuple:
"""
Imports a video clip from a file using ffmpeg.
This method reads a video file using ffmpeg, converts each frame to a PIL Image, and returns a tuple of the images and the fps of the video.
Args:
file_name (str): The name of the video file to import.
ffmpeg_options (dict | None, optional): Additional options to pass to ffmpeg. Defaults to None.
Returns:
tuple: A tuple of the frames as PIL Images and the fps of the video.
Raises:
None
Example:
>>> video_clip = VideoClip()
>>> frames, fps = video_clip._import_video_clip("video.mp4")
Note:
This method uses ffmpeg to read the video file.
"""
options = {**(ffmpeg_options if ffmpeg_options else {})}
fps, frames = ffmpegio.video.read(file_name, **options)
return tuple(Image.fromarray(frame) for frame in frames), fps

0 comments on commit 2fbb8dc

Please sign in to comment.