File size: 2,373 Bytes
3e3d7b1
 
 
 
 
 
 
fe7106b
3e3d7b1
 
 
fe7106b
 
 
 
3e3d7b1
 
 
 
9845310
 
fe7106b
 
 
 
 
 
 
 
 
 
 
 
 
 
9845310
 
 
fe7106b
 
9845310
 
 
fe7106b
9845310
3e3d7b1
 
 
 
 
9845310
 
 
3e3d7b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations


class TransitionBuilder:
    """Build FFmpeg xfade filters for scene joins."""

    TRANSITIONS = {
        "basic": "fade",
        "fade": "fade",
        "zoom": "zoomin",
        "slide": "slideleft",
        "slide_left": "slideleft",
        "slide_right": "slideright",
        "slide_up": "slideup",
        "slide_down": "slidedown",
        "push": "slideup",
        "blur": "fade",
        "whip": "smoothleft",
        "dissolve": "dissolve",
        "flash": "fadewhite",
        "glitch": "hlslice",
        "light_leak": "fadewhite",
        "film_burn": "fadegrays",
        "camera_shake": "smoothleft",
        "spin": "circleopen",
        "3d_flip": "vertopen",
        "flip": "vertopen",
        "cube": "rectcrop",
        "ripple": "radial",
        "ink": "distance",
        "morph": "dissolve",
        "stretch": "squeezeh",
        "liquid": "pixelize",
        "elastic": "smoothup",
        "motion_blur": "smoothleft",
        "wipe": "wipeleft",
        "wipe_left": "wipeleft",
        "wipe_right": "wiperight",
        "wipe_up": "wipeup",
        "wipe_down": "wipedown",
        "smooth_left": "smoothleft",
        "smooth_right": "smoothright",
        "fadeblack": "fadeblack",
        "fade_white": "fadewhite",
        "pixel": "pixelize",
    }

    def map_transition(self, name: str) -> str:
        return self.TRANSITIONS.get(name, "fade")

    def list_transitions(self) -> list[str]:
        return list(self.TRANSITIONS.keys())

    def xfade_chain(self, stream_count: int, durations: list[float], transitions: list[str], transition_duration: float = 0.45) -> tuple[str, str]:
        if stream_count <= 1:
            return "", "[0:v]"
        filters: list[str] = []
        current = "[0:v]"
        offset = max(0.1, durations[0] - transition_duration)
        for idx in range(1, stream_count):
            out = f"[vx{idx}]"
            transition = self.map_transition(transitions[idx - 1] if idx - 1 < len(transitions) else "fade")
            filters.append(
                f"{current}[{idx}:v]xfade=transition={transition}:duration={transition_duration}:offset={offset:.3f}{out}"
            )
            current = out
            if idx < len(durations):
                offset += max(0.1, durations[idx] - transition_duration)
        return ";".join(filters), current