Source code for veditor.elements.video

# coding: utf-8
import os
from typing import List, Optional, Tuple, Union

import cv2
import numpy as np
import numpy.typing as npt
from PIL import Image
from tqdm import tqdm

from ..utils.audio_utils import synthesize_audio
from ..utils.video_utils import capture2writor
from .base import BaseElement, FixedElement


[docs]class VideoElement(FixedElement): def __init__( self, video_path: str, start_pos: int = 0, margin: Union[int, List[int]] = 0, width: Optional[int] = None, height: Optional[int] = None, top: Optional[Union[BaseElement, int]] = 0, right: Optional[Union[BaseElement, int]] = None, left: Optional[Union[BaseElement, int]] = 0, bottom: Optional[Union[BaseElement, int]] = None, ): cap = cv2.VideoCapture(video_path) super().__init__( pos_frames=(start_pos, start_pos + int(cap.get(cv2.CAP_PROP_FRAME_COUNT))), margin=margin, width=width, height=height, top=top, right=right, left=left, bottom=bottom, **dict(video_path=video_path), # kwargs ) self.set_video_attributes(video_path=video_path) self.set_attribute(name="video_start_pos", value=start_pos)
[docs] def calc_element_size( self, video_path: Optional[str] = None, width: Optional[int] = None, height: Optional[int] = None, **kwargs, ) -> Tuple[int, int]: cap = cv2.VideoCapture(video_path) if width is None: width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) if height is None: height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) return (width, height)
[docs] def set_video_attributes(self, video_path: str) -> None: cap = cv2.VideoCapture(video_path) self.set_attribute( name="frame_count", value=int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) ) self.set_attribute(name="fps", value=cap.get(cv2.CAP_PROP_FPS)) self.set_attribute(name="video_path", value=video_path)
[docs] def set_fps(self, fps: float) -> None: self.set_attribute(name="fps", value=fps, msg=f"Changed fps to {fps}")
@property def video_filename(self) -> str: """Final component of a pathname of ``video_path``.""" return os.path.basename(self.video_path)
[docs] def append(self, element: BaseElement) -> None: """Append a new :class:`element <veditor.elements.base.BaseElement>` Args: element (BaseElement) : An instance of :class:`BaseElement <veditor.elements.base.BaseElement>`. """ self.elements.append(element)
[docs] def edit(self, frame: npt.NDArray[np.uint8], pos: int) -> npt.NDArray[np.uint8]: """Edit a ``pos``-th frame in the video ``vide_path``. Args: frame (npt.NDArray[np.uint8]) : The current frame (BGR image) in the video. pos (int) : The current position in the video. Returns: npt.NDArray[np.uint8]: An editied frame. """ if self.inCharge(pos): cap = cv2.VideoCapture(self.video_path) cap.set(cv2.CAP_PROP_POS_FRAMES, pos - self.pos) ret, video_frame = cap.read() if video_frame is not None: frame[self.top : self.bottom, self.left : self.right, :] = video_frame return frame
[docs] def check_work( self, pos: int, as_pil: bool = True ) -> Union[npt.NDArray[np.uint8], Image.Image]: """Check the editing result for ``pos`` frame in video at ``video_path`` of this editor. Args: pos (int) : The position in the video. as_pil (bool, optional) : Whether to return object as ``Image.Image`` or ``npt.NDArray[npt.uint8]``. Defaults to ``True``. Returns: Union[npt.NDArray[np.uint8], Image.Image]: An editing result for the ``pos``-th frame. """ return super().check_work(video_path=self.video_path, pos=pos, as_pil=as_pil)
[docs] def check_works( self, audio_path: Optional[str] = None, out_path: Optional[str] = None, codec: str = "H264", H: Optional[int] = None, W: Optional[int] = None, fps: Optional[float] = None, open: bool = True, **kwargs, ) -> str: """Check the editing results of this editor. Args: audio_path (str, optional) : Path to the audio file. Defaults to ``None``. (Same as ``video_path``.) out_path (Optional[str], optional) : Path to the created video. Defaults to ``None``. codec (str, optional) : Video codec for the created video. Defaults to ``"H264"``. H (Optional[int], optional) : Height of the output video. Defaults to ``None``. W (Optional[int], optional) : Width of the output video. Defaults to ``None``. fps (Optional[float], optional) : Frame rate of the output video. Defaults to ``None``. open (bool, optional) : Whether to open output file or not. Defaults to ``True``. Returns: str: The path to the created video. """ return super().check_works( video_path=self.video_path, audio_path=audio_path, out_path=out_path, codec=codec, H=H, W=W, open=open, **kwargs, )
[docs] def export( self, out_path: Optional[str] = None, codec: str = "H264", H: Optional[int] = None, W: Optional[int] = None, fps: Optional[float] = None, open: bool = True, **kwargs, ) -> str: """Create a video with each element in ``elements``. Args: out_path (Optional[str], optional) : Path to the output video. Defaults to ``None``. codec (str, optional) : Video codec for the output video. Defaults to ``"H264"``. H (Optional[int], optional) : Height of the output video. Defaults to ``None``. W (Optional[int], optional) : Width of the output video. Defaults to ``None``. fps (Optional[float], optional) : Frame rate of the output video. Defaults to ``None``. open (bool, optional) : Whether to open the created video file. Defaults to ``True``. Returns: str: The path to the created video file. """ cap = cv2.VideoCapture(self.video_path) cap.set(cv2.CAP_PROP_FPS, self.fps) out, out_path = capture2writor( cap=cap, out_path=out_path, codec=codec, H=H, W=W, fps=fps ) for i in tqdm( range(int(cap.get(cv2.CAP_PROP_FRAME_COUNT))), desc=self.video_filename ): ret, frame = cap.read() if (not ret) or (frame is None): break frame = self.edit(frame=frame, pos=i) out.write(frame) out.release() cap.release() return self.synthesize_audio(out_path=out_path, open=open)
[docs] def synthesize_audio(self, out_path: str, open: bool = True) -> str: """Create audio with each editor in ``editors`` and attach it to the video at ``out_path``. Args: out_path (str) : The path to the output video. open (bool, optional) : Whether to open the created video file. Defaults to ``True``. Returns: str: The path to the created video file. """ audio_path = self.video_path for element in self.elements: audio_path = element.overlayed_audio_create( video_path=audio_path, fps=self.fps ) synthesized_video_path = synthesize_audio( video_path=out_path, audio_path=audio_path, open=open, ) return synthesize_audio
[docs] def overlayed_audio_create(self, video_path: str, fps: float) -> str: for element in self.elements: video_path = element.overlayed_audio_create(video_path=video_path, fps=fps) return video_path