Python: captionbob.render
captionbob.render.preview_layers
The emoji layers of a video version for the live preview.
The burn draws each emoji layer with the ffmpeg filters scale and overlay, in captionbob/render/burn.py. The live preview in the browser draws the ASS script with libass, but it has no ffmpeg for the layers. So the library works out the box of each layer on each frame of the video from the same ffmpeg expressions, and the browser draws the image in that box.
A layer is on the frames whose time, cut to whole milliseconds, is in the time of the layer, as with the overlay of the burn. On each of these frames:
- The side of the image is the size expression at the time t of the frame, cut to whole pixels, as the filter scale cuts it.
- The left column and the top row are the left and the top expressions at the time t, with the side as w and h and the video size as W and H. Each is cut to whole pixels, and an odd one moves one pixel to the left or up, as the filter overlay places an image on a video in its format yuv420.
Frame n has the time n / frame rate. ffmpeg works out that time with floating point numbers. So on a frame whose time is a whole millisecond, ffmpeg can start or end a layer one frame later than this module does.
The record of one layer is:
{"image": the path of the image in the emoji index folder, with /,
"start_seconds": the time at which the image starts to play,
"first_frame": the number of the first frame of the layer, from 0,
"boxes": [[left, top, side], ...], one box for each frame of the
layer from the first frame on}
An animated image plays from its first frame at start_seconds, again and again, as in the burn.
Source file: captionbob/render/preview_layers.py
first_frame_at
def first_frame_at(milliseconds: int, frame_rate: Fraction) -> int
Give the first frame whose time, cut to whole milliseconds, is a time or later.
Frame n has the time n * 1000 / frame rate in milliseconds. That time, cut, is the given time or later when the time itself is.
Args:
milliseconds: The time in whole milliseconds.frame_rate: The frames each second of the video.
Returns:
The number of the frame, 0 or above.
layer_frames
def layer_frames(layer: ImageLayer, frame_rate: Fraction) -> range
Give the frames on which the overlay of the burn draws a layer.
Args:
layer: The image layer.frame_rate: The frames each second of the video.
Returns:
The numbers of the frames, which can be none.
layer_boxes
def layer_boxes(
layer: ImageLayer, video: VideoInfo) -> tuple[range, list[list[int]]]
Give the box of the image of a layer on each frame of the layer.
Args:
layer: The image layer.video: The frame size and the frame rate of the video.
Returns:
The frames of the layer, and for each frame the left column, the top row and the side of the image.
Raises:
ValueError: When an expression cannot be read, uses a name that the filter does not give, or gives a side below 1 pixel or a value that is not a number of pixels.
image_in_folder
def image_in_folder(image_path: Path, index_folder: Path) -> str
Give the path of an emoji image inside the emoji index folder.
Args:
image_path: The image of a layer.index_folder: The emoji index folder.
Returns:
The path from the folder, with / between its parts.
Raises:
ValueError: When the image is not in the folder.
preview_layer_records
def preview_layer_records(
layers: Sequence[ImageLayer], index_folder: Path,
video: VideoInfo) -> list[dict[str, Any]]
Give the record of each emoji layer for the live preview.
Args:
layers: The emoji layers of the burn, in their draw order.index_folder: The emoji index folder of their images.video: The frame size and the frame rate of the video.
Returns:
One record for each layer, in the same order, in the form of the module text.
Raises:
ValueError: When an image is not in the folder, or when the box of a layer cannot be worked out.