File size: 7,141 Bytes
6755a2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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