File size: 11,395 Bytes
a57c6eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
from functools import partial
from copy import deepcopy
from typing import Iterable, List, Tuple, Union
import bisect
import logging

import numpy as np


from .clip import Clip, ClipSeq
from .clipid import ClipIds, ClipIdsSeq, MatchedClipIds, MatchedClipIdsSeq

logger = logging.getLogger(__name__)  # pylint: disable=invalid-name

__all__ = [
    "find_idx_by_rela_time",
    "find_idx_by_time",
    "find_idx_by_clip",
    "get_subseq_by_time",
    "get_subseq_by_idx",
    "clip_is_top",
    "clip_is_middle",
    "clip_is_end",
    "abadon_old_return_new",
    "reset_clipseq_id",
    "insert_endclip",
    "insert_startclip",
    "drop_start_end_by_time",
    "complete_clipseq",
    "complete_gap",
    "get_subseq_by_stages",
    "find_time_by_stage",
]


def find_idx_by_rela_time(clipseq: ClipSeq, timepoint: float) -> int:
    clipseq_duration = clipseq.duration
    timepoint = clipseq_duration * timepoint
    clipseq_times = [c.duration for c in clipseq]
    clipseq_times.insert(0, 0)
    clipseq_times = np.cumsum(clipseq_times)
    idx = bisect.bisect_right(clipseq_times, timepoint)
    idx = min(max(0, idx - 1), len(clipseq) - 1)
    return idx


def find_idx_by_time(clipseq: ClipSeq, timepoint: float) -> int:
    """寻找指定时间timepoint 在 clipseq 中的片段位置

    Args:
        clipseq (ClipSeq): 待寻找的片段序列
        timepoint (float): 指定时间位置

    Returns:
        _type_: _description_
    """
    clipseq_times = [c.time_start for c in clipseq]
    idx = bisect.bisect_right(clipseq_times, timepoint)
    idx = min(max(0, idx - 1), len(clipseq) - 1)
    return idx


def find_idx_by_clip(clipseq: ClipSeq, clip: Clip, eps: float = 1e-4) -> int:
    """通过计算目标clip和clipseq中所有候选clip的交集占比来找最近clip

    Args:
        clipseq (ClipSeq): 候选clip序列
        clip (Clip): 目标clip
        eps (float, optional): 最小交集占比. Defaults to 1e-4.

    Returns:
        int: 目标clip在候选clip序列的位置,若无则为None
    """
    timepoints = np.array([[c.time_start, c.time_start + c.duration] for c in clipseq])
    clip_time_start = clip.time_start
    clip_duraiton = clip.duration
    clip_time_end = clip_time_start + clip_duraiton
    max_time_start = np.maximum(timepoints[:, 0], clip_time_start)
    min_time_end = np.minimum(timepoints[:, 1], clip_time_end)
    intersection = min_time_end - max_time_start
    intersection_ratio = intersection / clip_duraiton
    max_intersection_ratio = np.max(intersection_ratio)
    idx = np.argmax(intersection_ratio) if max_intersection_ratio > eps else None
    return idx


def get_subseq_by_time(
    clipseq: ClipSeq,
    start: float = 0,
    duration: float = None,
    end: float = 1,
    eps: float = 1e-2,
) -> ClipSeq:
    """根据时间对媒体整体做掐头去尾,保留中间部分。,也可以是大于1的数。
        start和end如果是0-1的小数,则认为是是相对时间位置,实际位置会乘以duration;
        start和end如果是大于1的数,则是绝对时间位置。

    Args:
        clipseq (ClipSeq): 待处理的序列
        start (float,): 保留部分的开始,. Defaults to 0.
        duration (float, optional): 媒体文件当前总时长
        end (float, optional): 保留部分的结尾. Defaults to 1.

    Returns:
        ClipSeq: 处理后的序列
    """
    if (start == 0 or start is None) and (end is None or end == 1):
        logger.warning("you should set start or end")
        return clipseq
    if duration is None:
        duration = clipseq.duration
    if start is None or start == 0:
        clip_start_idx = 0
    else:
        if start < 1:
            start = start * duration
        clip_start_idx = find_idx_by_time(clipseq, start)
    if end is None or end == 1 or np.abs(duration - end) < eps:
        clip_end_idx = -1
    else:
        if end < 1:
            end = end * duration
        clip_end_idx = find_idx_by_time(clipseq, end)
    if clip_end_idx != -1 and clip_start_idx >= clip_end_idx:
        logger.error(
            f"clip_end_idx({clip_end_idx}) should be > clip_start_idx({clip_start_idx})"
        )
    subseq = get_subseq_by_idx(clipseq, clip_start_idx, clip_end_idx)
    return subseq


def get_subseq_by_idx(clipseq: ClipSeq, start: int = None, end: int = None) -> ClipSeq:
    """通过指定索引范围,切片子序列

    Args:
        clipseq (ClipSeq):
        start (int, optional): 开始索引. Defaults to None.
        end (int, optional): 结尾索引. Defaults to None.

    Returns:
        _type_: _description_
    """
    if start is None and end is None:
        return clipseq
    if start is None:
        start = 0
    if end is None:
        end = len(clipseq)
    return clipseq[start:end]


def clip_is_top(clip: Clip, total: float, th: float = 0.1) -> bool:
    """判断Clip是否属于开始部分

    Args:
        clip (Clip):
        total (float): 所在ClipSeq总时长
        th (float, optional): 开始范围的截止位置. Defaults to 0.05.

    Returns:
        Bool: 是不是头部Clip
    """
    clip_time = clip.time_start
    if clip_time / total <= th:
        return True
    else:
        return False


def clip_is_end(clip: Clip, total: float, th: float = 0.9) -> bool:
    """判断Clip是否属于结尾部分

    Args:
        clip (Clip):
        total (float): 所在ClipSeq总时长
        th (float, optional): 结尾范围的开始位置. Defaults to 0.9.

    Returns:
        Bool: 是不是尾部Clip
    """
    clip_time = clip.time_start + clip.duration
    if clip_time / total >= th:
        return True
    else:
        return False


def clip_is_middle(
    clip: Clip, total: float, start: float = 0.05, end: float = 0.9
) -> bool:
    """判断Clip是否属于中间部分

    Args:
        clip (Clip):
        total (float): 所在ClipSeq总时长
        start (float, optional): 中间范围的开始位置. Defaults to 0.05.
        start (float, optional): 中间范围的截止位置. Defaults to 0.9.

    Returns:
        Bool: 是不是中间Clip
    """
    if start >= 0 and start < 1:
        start = total * start
    if end > 0 and end <= 1:
        end = total * end
    clip_time_start = clip.time_start
    clip_time_end = clip.time_start + clip.duration
    if (clip_time_start >= start) and (clip_time_end <= end):
        return True
    else:
        return False


def abadon_old_return_new(s1: Clip, s2: Clip) -> Clip:
    """特殊的融合方式
    Args:

        s1 (Clip): 靠前的clip
        s2 (Clip): 靠后的clip

    Returns:
        Clip: 融合后的Clip
    """
    return s2


# TODO:待确认是否要更新clipid,不方便对比着json进行debug
def reset_clipseq_id(clipseq: ClipSeq) -> ClipSeq:
    for i in range(len(clipseq)):
        if isinstance(clipseq[i], dict):
            clipseq[i]["clipid"] = i
        else:
            clipseq[i].clipid = i
    return clipseq


def insert_startclip(clipseq: ClipSeq) -> ClipSeq:
    """给ClipSeq插入一个开始片段。

    Args:
        clipseq (ClipSeq):
        clip_class (Clip, optional): 插入的Clip类型. Defaults to Clip.

    Returns:
        ClipSeq: 插入头部Clip的新ClipSeq
    """
    if clipseq[0].time_start > 0:
        start = clipseq.ClipClass(
            time_start=0, duration=round(clipseq[0].time_start, 3), timepoint_type=0
        )
        clipseq.insert(0, start)
    clipseq = reset_clipseq_id(clipseq)
    return clipseq


def insert_endclip(clipseq: ClipSeq, duration: float) -> ClipSeq:
    """给ClipSeq插入一个尾部片段。

    Args:
        clipseq (ClipSeq):
        duration(float, ): 序列的总时长
        clip_class (Clip, optional): 插入的Clip类型. Defaults to Clip.

    Returns:
        ClipSeq: 插入尾部Clip的新ClipSeq
    """
    clipseq_endtime = clipseq[-1].time_start + clipseq[-1].duration
    if duration - clipseq_endtime > 1:
        end = clipseq.ClipClass(
            time_start=round(clipseq_endtime, 3),
            duration=round(duration - clipseq_endtime, 3),
            timepoint_type=0,
        )
        clipseq.append(end)
    clipseq = reset_clipseq_id(clipseq)
    return clipseq


def drop_start_end_by_time(
    clipseq: ClipSeq, start: float, end: float, duration: float = None
):
    return get_subseq_by_time(clipseq=clipseq, start=start, end=end, duration=duration)


def complete_clipseq(
    clipseq: ClipSeq, duration: float = None, gap_th: float = 2
) -> ClipSeq:
    """绝大多数需要clipseq中的时间信息是连续、完备的,有时候是空的,需要补足的部分。
    如歌词时间戳生成的music_map缺头少尾、中间有空的部分。

    Args:
        clipseq (ClipSeq): 待补集的序列
        duration (float, optional): 整个序列持续时间. Defaults to None.
        gap_th (float, optional): 有时候中间空隙过短就会被融合到上一个片段中. Defaults to 2.

    Returns:
        ClipSeq: 补集后的序列,时间连续、完备。
    """
    if isinstance(clipseq, list):
        clipseq = ClipSeq(clipseq)
        return complete_clipseq(clipseq=clipseq, duration=duration, gap_th=gap_th)
    clipseq = complete_gap(clipseq, th=gap_th)
    clipseq = insert_startclip(clipseq)
    if duration is not None:
        clipseq = insert_endclip(clipseq, duration)
    return clipseq


def complete_gap(clipseq: ClipSeq, th: float = 2) -> ClipSeq:
    """generate blank clip timepoint = 0,如果空白时间过短,则空白附到上一个歌词片段中。


    Args:
        clipseq (ClipSeq): 原始的歌词生成的MusicClipSeq
        th (float, optional): 有时候中间空隙过短就会被融合到上一个片段中. Defaults to 2.

    Returns:
        ClipSeq: 补全后的
    """
    gap_clipseq = []
    clipid = 0
    for i in range(len(clipseq) - 1):
        time_start = clipseq[i].time_start
        duration = clipseq[i].duration
        time_end = time_start + duration
        next_time_start = clipseq[i + 1].time_start
        time_diff = next_time_start - time_end
        if time_diff >= th:
            blank_clip = clipseq.ClipClass(
                time_start=time_end,
                duration=time_diff,
                timepoint_type=0,
                clipid=clipid,
            )
            gap_clipseq.append(blank_clip)
            clipid += 1
        else:
            clipseq[i].duration = next_time_start - time_start
    clipseq.extend(gap_clipseq)
    clipseq.clips = sorted(clipseq.clips, key=lambda clip: clip.time_start)
    reset_clipseq_id(clipseq)
    return clipseq


def find_time_by_stage(
    clipseq: ClipSeq, stages: Union[str, List[str]] = None
) -> Tuple[float, float]:
    if isinstance(stages, list):
        stages = [stages]
    for clip in clipseq:
        if clip.stage in stages:
            return clip.time_start, clip.time_end
    return None, None


def get_subseq_by_stages(clipseq: ClipSeq, stages: Union[str, List[str]]) -> ClipSeq:
    if isinstance(stages, List):
        stages = [stages]
    start, _ = find_time_by_stage(clipseq, stages[0])
    _, end = find_time_by_stage(clipseq, stages[-1])
    if start1 is None:
        start1 = 0
    if end2 is None:
        end2 = clipseq.duration
    subseq = get_subseq_by_time(clipseq=clipseq, start=start, end=end)
    return subseq