| 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 | |