Spaces:
No application file
No application file
import math | |
from heapq import nsmallest | |
import logging | |
import numpy as np | |
import cv2 | |
from moviepy.editor import ( | |
VideoFileClip, | |
VideoClip, | |
concatenate_videoclips, | |
vfx, | |
TextClip, | |
CompositeVideoClip, | |
) | |
from ..utils.vision_util import ( | |
cal_crop_coord, | |
round_up_coord_to_even, | |
cal_small_bbox_coord_of_big_bbox, | |
) | |
logger = logging.getLogger(__name__) # pylint: disable=invalid-name | |
class VideoClipOperator(object): | |
def __init__(self, *args, **kwds) -> None: | |
pass | |
def __call__(self, *args, **kwds): | |
pass | |
def get_subclip_from_clipseq_by_time(): | |
pass | |
def get_mvpclip_from_clip_by_time( | |
clips, final_duration: float, method: str = None, delta=0 | |
): | |
"""根据视频长度,对齐到指定长度 | |
Args: | |
clips (VideoClipSeq): 媒体文件片段序列 | |
final_duration (float): 目标长度 | |
method (int, optional): how to chang video length. Defaults to `None`. | |
speed: chang length by sample | |
cut: change length by cut middle length | |
None: change length accorrding difference of clip duration and final_duration. Defaults to None. | |
Returns: | |
VideoClip: 读取、对齐后moviepy VideoClip | |
""" | |
n_clips = len(clips) | |
video_clips = [] | |
for i, clip in enumerate(clips): | |
start_delta = 0 | |
end_delta = 0 | |
# TODO: 为了解决夹帧问题,视视觉片段长音乐片段一些,便于只取中间部分。 | |
## 适用于多个视频源的片段 | |
## 适用于同一个视频源的 多个连续片段 | |
if n_clips > 1: | |
if i == 0: | |
start_delta = delta | |
if i == n_clips - 1: | |
end_delta = delta | |
else: | |
start_delta = delta | |
end_delta = delta | |
video_clip = clip.get_mvp_clip(start_delta=start_delta, end_delta=end_delta) | |
video_clips.append(video_clip) | |
video_clips = concatenate_videoclips(clips=video_clips, method="compose") | |
video_clips = get_sub_mvpclip_by_time( | |
clip=video_clips, final_duration=final_duration, method=method | |
) | |
return video_clips | |
def get_sub_mvpclip_by_time( | |
clip, final_duration: float, method: str = "speed", center_ratio: float = 0.5 | |
): | |
duration = clip.duration | |
center = duration * center_ratio | |
center = min(max(center, final_duration / 2), duration - final_duration / 2) | |
if method == "speed": | |
clip = clip.fx(vfx.speedx, final_duration=final_duration) | |
elif method == "cut" or method is None: | |
if duration >= final_duration: | |
t_start = center - final_duration / 2 | |
t_end = center + final_duration / 2 | |
clip = clip.subclip(t_start, t_end) | |
logger.debug( | |
"[cut_clip_time]: change length by cut: t_start={:.3f}, t_end={:.3f}, duration={:.3f}, final_duration={:.3f}".format( | |
t_start, t_end, duration, final_duration | |
) | |
) | |
clip = clip.fx(vfx.speedx, final_duration=final_duration) | |
else: | |
raise NotImplementedError( | |
"var_video_clip_length do not support mode={}".format(clip) | |
) | |
return clip | |
def crop_by_ratio( | |
clip, target_width_height_ratio, restricted_bbox=None, need_round2even=False | |
): | |
"""将原视频中的有效部分剪辑成目标宽高比,有效部分用坐标表示,一般来说是非黑边、非水印位置 | |
Args: | |
clip (VideoClip): moviepy中的视频片段 | |
target_width_height_ratio (float): 目标宽高比,常见的有2.35, 1.777, 0.75, 1, 0.5625 | |
restricted_bbox ((float, float, float, float), optional): (x1, y1, x2, y2). Defaults to None. | |
Returns: | |
VideoClip: 剪辑好的moviepy视频片段 | |
""" | |
width = clip.w | |
height = clip.h | |
target_coord = cal_crop_coord( | |
width=width, | |
height=height, | |
target_width_height_ratio=target_width_height_ratio, | |
restricted_bbox=restricted_bbox, | |
) | |
if need_round2even: | |
target_coord = round_up_coord_to_even(*target_coord) | |
clip = clip.crop(*target_coord) | |
return clip | |
def crop_by_perception( | |
clip, | |
target_width_height_ratio: float, | |
perception: dict, | |
need_round2even: bool = True, | |
): | |
"""将原视频中的有效部分剪辑成目标宽高比,有效部分用坐标表示,一般来说是非黑边、非水印位置 | |
Args: | |
clip (VideoClip): moviepy中的视频片段 | |
target_width_height_ratio (float): 目标宽高比,常见的有2.35, 1.777, 0.75, 1, 0.5625 | |
Returns: | |
VideoClip: 剪辑好的moviepy视频片段 | |
""" | |
return crop_by_face_clip( | |
clip, target_width_height_ratio, perception, need_round2even | |
) | |
def crop_by_face_clip( | |
clip, | |
target_width_height_ratio: float, | |
perception, | |
need_round2even: bool = True, | |
topk: int = 1, | |
): | |
w = clip.w | |
h = clip.h | |
target_w = target_width_height_ratio * h | |
perception_objs = [] | |
if len(perception) > 0: | |
for i, frame_perception in enumerate(perception.clips): | |
if frame_perception.objs is not None: | |
for obj in frame_perception.objs: | |
perception_objs.append({"bbox": obj.bbox, "trackid": obj.trackid}) | |
# 如果没有目标人物,则依然使用中间crop方式 | |
if len(perception) == 0 or len(perception_objs) == 0: | |
return crop_by_ratio( | |
clip, target_width_height_ratio, need_round2even=need_round2even | |
) | |
topk_rolid = nsmallest(topk, [obj["trackid"] for obj in perception_objs]) | |
topk_clip = [obj for obj in perception_objs if obj["trackid"] in topk_rolid] | |
# TODO: topk_clip 具有时间的先后顺序,先暂定取中间的obj的框作为参考 | |
target_idx = int(len(topk_clip) // 2) | |
x1, y1, x2, y2 = topk_clip[target_idx]["bbox"] | |
# TODO:当前适用于 target_w 大于 obj_width对应的人体宽度,当不符合条件时存在crop部分人体部分情况,此时应该提前过滤。 | |
obj_width = x2 - x1 | |
obj_height = y2 - y1 | |
obj_center_width = (x1 + x2) / 2 | |
obj_center_height = (y1 + y2) / 2 | |
target_coord = cal_small_bbox_coord_of_big_bbox( | |
bigbbox_width=w, | |
bigbbox_height=h, | |
smallbbox_width=target_w, | |
smallbbox_height=obj_height, | |
center_width=obj_center_width, | |
center_height=obj_center_height, | |
need_round2even=need_round2even, | |
) | |
clip = clip.mv.crop(*target_coord) | |
return clip | |
def crop_target_bbox(clip, target_coord, need_round2even=False): | |
if need_round2even: | |
target_coord = round_up_coord_to_even(*target_coord) | |
clip = clip.crop(*target_coord) | |
return clip | |
def crop_edge_2_even(clip): | |
w, h = clip.w, clip.h | |
# logger.debug("crop_target_bbox-round_up_coord_to_even, before {} {} {} {}".format(0, 0, w, h)) | |
target_coord = round_up_coord_to_even(0, 0, w, h) | |
# logger.debug("crop_target_bbox-round_up_coord_to_even, after {} {} {} {}".format(target_coord[0], target_coord[1], target_coord[2], target_coord[3])) | |
clip = clip.crop(*target_coord) | |
return clip | |