euiia commited on
Commit
640c04f
·
verified ·
1 Parent(s): 377170b

Create video_encode_tool.py

Browse files
Files changed (1) hide show
  1. tools/video_encode_tool.py +143 -0
tools/video_encode_tool.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tools/video_encode_tool.py
2
+ #
3
+ # Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos
4
+ #
5
+ # Version: 1.1.0
6
+ #
7
+ # This file defines the VideoEncodeTool specialist. Its purpose is to abstract away
8
+ # the underlying command-line tools (like FFmpeg) used for video manipulation tasks
9
+ # such as concatenation and creating transitions. By encapsulating this logic, the core
10
+ # Deformes4D engine can remain agnostic to the specific tool being used.
11
+
12
+ import os
13
+ import subprocess
14
+ import logging
15
+ import gradio as gr
16
+ from typing import List, Optional, Tuple
17
+ import random
18
+ import time
19
+ import shutil
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ class VideoEncodeTool:
24
+ """
25
+ A specialist for handling video encoding and manipulation tasks.
26
+ Currently uses FFmpeg as the backend.
27
+ """
28
+
29
+ def create_transition_bridge(self, start_image_path: str, end_image_path: str,
30
+ duration: float, fps: int, target_resolution: Tuple[int, int],
31
+ workspace_dir: str, effect: Optional[str] = None) -> str:
32
+ """
33
+ Creates a short video clip that transitions between two static images using FFmpeg's xfade filter.
34
+ This is useful for creating a "bridge" during a hard "cut" decided by the cinematic director.
35
+
36
+ Args:
37
+ start_image_path (str): The file path to the starting image.
38
+ end_image_path (str): The file path to the ending image.
39
+ duration (float): The desired duration of the transition in seconds.
40
+ fps (int): The frames per second for the output video.
41
+ target_resolution (Tuple[int, int]): The (width, height) of the output video.
42
+ workspace_dir (str): The directory to save the output video.
43
+ effect (Optional[str], optional): The specific xfade effect to use. If None, a random
44
+ effect is chosen. Defaults to None.
45
+
46
+ Returns:
47
+ str: The file path to the generated transition video clip.
48
+ """
49
+ output_path = os.path.join(workspace_dir, f"bridge_{int(time.time())}.mp4")
50
+ width, height = target_resolution
51
+
52
+ fade_effects = [
53
+ "fade", "wipeleft", "wiperight", "wipeup", "wipedown", "dissolve",
54
+ "fadeblack", "fadewhite", "radial", "rectcrop", "circleopen",
55
+ "circleclose", "horzopen", "horzclose"
56
+ ]
57
+
58
+ selected_effect = effect if effect and effect.strip() else random.choice(fade_effects)
59
+
60
+ # The duration of each image loop and the xfade itself should match the total desired duration.
61
+ transition_duration = max(0.1, duration) # Ensure duration is not zero
62
+
63
+ # Construct the FFmpeg command
64
+ # -v error: Suppress all console output except for errors.
65
+ # -loop 1: Loop the input image.
66
+ # -t {duration}: Set the duration for the looped image.
67
+ # -filter_complex: Defines a complex filtergraph.
68
+ # - scale,setsar: Pre-process each image to the target resolution and aspect ratio.
69
+ # - xfade: Apply the crossfade transition.
70
+ # - offset=0: Start the transition immediately.
71
+ # -map "[out]": Map the output of the filtergraph to the final video.
72
+ # -c:v libx264 -r {fps} -pix_fmt yuv420p: Standard high-compatibility video encoding settings.
73
+ cmd = (
74
+ f"ffmpeg -y -v error -loop 1 -t {transition_duration} -i \"{start_image_path}\" -loop 1 -t {transition_duration} -i \"{end_image_path}\" "
75
+ f"-filter_complex \"[0:v]scale={width}:{height},setsar=1[v0];[1:v]scale={width}:{height},setsar=1[v1];"
76
+ f"[v0][v1]xfade=transition={selected_effect}:duration={transition_duration}:offset=0[out]\" "
77
+ f"-map \"[out]\" -c:v libx264 -r {fps} -pix_fmt yuv420p \"{output_path}\""
78
+ )
79
+
80
+ logger.info(f"Creating FFmpeg transition bridge with effect: '{selected_effect}' | Duration: {transition_duration}s")
81
+
82
+ try:
83
+ subprocess.run(cmd, shell=True, check=True, text=True)
84
+ except subprocess.CalledProcessError as e:
85
+ logger.error(f"FFmpeg bridge creation failed. Return code: {e.returncode}")
86
+ logger.error(f"FFmpeg command: {cmd}")
87
+ logger.error(f"FFmpeg stderr: {e.stderr}")
88
+ raise gr.Error(f"Failed to create transition video. Details: {e.stderr}")
89
+
90
+ return output_path
91
+
92
+ def concatenate_videos(self, video_paths: List[str], output_path: str, workspace_dir: str):
93
+ """
94
+ Concatenates multiple video clips into a single file without re-encoding.
95
+
96
+ Args:
97
+ video_paths (List[str]): A list of absolute paths to the video clips to be concatenated.
98
+ output_path (str): The absolute path for the final output video.
99
+ workspace_dir (str): The directory to use for temporary files, like the concat list.
100
+ """
101
+ if not video_paths:
102
+ raise gr.Error("VideoEncodeTool: No video fragments provided for concatenation.")
103
+
104
+ if len(video_paths) == 1:
105
+ logger.info("Only one video clip found. Skipping concatenation and just copying the file.")
106
+ # If there's only one clip, a simple copy is much faster.
107
+ shutil.copy(video_paths[0], output_path)
108
+ return
109
+
110
+ list_file_path = os.path.join(workspace_dir, "concat_list.txt")
111
+
112
+ try:
113
+ with open(list_file_path, 'w', encoding='utf-8') as f:
114
+ for path in video_paths:
115
+ f.write(f"file '{os.path.abspath(path)}'\n")
116
+
117
+ cmd_list = ['ffmpeg', '-y', '-f', 'concat', '-safe', '0', '-i', list_file_path, '-c', 'copy', output_path]
118
+
119
+ logger.info(f"Concatenating {len(video_paths)} video clips into {output_path} using FFmpeg...")
120
+
121
+ subprocess.run(cmd_list, check=True, capture_output=True, text=True)
122
+
123
+ logger.info(f"FFmpeg concatenation successful. Final video is at: {output_path}")
124
+
125
+ except subprocess.CalledProcessError as e:
126
+ logger.error(f"FFmpeg concatenation failed. Return code: {e.returncode}")
127
+ logger.error(f"FFmpeg stderr: {e.stderr}")
128
+ raise gr.Error(f"Failed to assemble the final video using FFmpeg. Details: {e.stderr}")
129
+ except Exception as e:
130
+ logger.error(f"An unexpected error occurred during video concatenation: {e}", exc_info=True)
131
+ raise gr.Error("An unexpected error occurred during the final video assembly.")
132
+ finally:
133
+ if os.path.exists(list_file_path):
134
+ os.remove(list_file_path)
135
+
136
+
137
+ # --- Singleton Instance ---
138
+ # We create a single instance of the tool to be imported by other modules.
139
+ video_encode_tool_singleton = VideoEncodeTool()```
140
+
141
+ O especialista `VideoEncodeTool` está agora mais poderoso. O `Deformes4D_engine` poderá chamá-lo tanto para montar os clipes gerados pelo LTX quanto para criar transições rápidas e eficientes para os "cortes", deixando a lógica de geração de latentes focada nos segmentos que precisam de movimento complexo.
142
+
143
+ Essa abstração está deixando nosso `Deformes4D_engine` cada vez mais limpo e focado em sua tarefa de orquestração de alto nível.