Update tools/video_encode_tool.py
Browse files- tools/video_encode_tool.py +22 -23
tools/video_encode_tool.py
CHANGED
|
@@ -2,24 +2,28 @@
|
|
| 2 |
#
|
| 3 |
# Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos
|
| 4 |
#
|
| 5 |
-
# Version: 1.1.
|
| 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.
|
|
@@ -45,6 +49,9 @@ class VideoEncodeTool:
|
|
| 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
|
|
@@ -57,19 +64,8 @@ class VideoEncodeTool:
|
|
| 57 |
|
| 58 |
selected_effect = effect if effect and effect.strip() else random.choice(fade_effects)
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 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];"
|
|
@@ -85,7 +81,7 @@ class VideoEncodeTool:
|
|
| 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
|
| 89 |
|
| 90 |
return output_path
|
| 91 |
|
|
@@ -97,13 +93,15 @@ class VideoEncodeTool:
|
|
| 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
|
| 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 |
|
|
@@ -125,14 +123,15 @@ class VideoEncodeTool:
|
|
| 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
|
| 129 |
except Exception as e:
|
| 130 |
logger.error(f"An unexpected error occurred during video concatenation: {e}", exc_info=True)
|
| 131 |
-
raise
|
| 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.
|
|
|
|
|
|
| 2 |
#
|
| 3 |
# Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos
|
| 4 |
#
|
| 5 |
+
# Version: 1.1.1
|
| 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, allowing for easier
|
| 11 |
+
# maintenance and future replacement with other libraries or tools.
|
| 12 |
|
| 13 |
import os
|
| 14 |
import subprocess
|
| 15 |
import logging
|
|
|
|
|
|
|
| 16 |
import random
|
| 17 |
import time
|
| 18 |
import shutil
|
| 19 |
+
from typing import List, Optional, Tuple
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
| 23 |
+
class VideoToolError(Exception):
|
| 24 |
+
"""Custom exception for errors originating from the VideoEncodeTool."""
|
| 25 |
+
pass
|
| 26 |
+
|
| 27 |
class VideoEncodeTool:
|
| 28 |
"""
|
| 29 |
A specialist for handling video encoding and manipulation tasks.
|
|
|
|
| 49 |
|
| 50 |
Returns:
|
| 51 |
str: The file path to the generated transition video clip.
|
| 52 |
+
|
| 53 |
+
Raises:
|
| 54 |
+
VideoToolError: If the FFmpeg command fails.
|
| 55 |
"""
|
| 56 |
output_path = os.path.join(workspace_dir, f"bridge_{int(time.time())}.mp4")
|
| 57 |
width, height = target_resolution
|
|
|
|
| 64 |
|
| 65 |
selected_effect = effect if effect and effect.strip() else random.choice(fade_effects)
|
| 66 |
|
| 67 |
+
transition_duration = max(0.1, duration)
|
| 68 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
cmd = (
|
| 70 |
f"ffmpeg -y -v error -loop 1 -t {transition_duration} -i \"{start_image_path}\" -loop 1 -t {transition_duration} -i \"{end_image_path}\" "
|
| 71 |
f"-filter_complex \"[0:v]scale={width}:{height},setsar=1[v0];[1:v]scale={width}:{height},setsar=1[v1];"
|
|
|
|
| 81 |
logger.error(f"FFmpeg bridge creation failed. Return code: {e.returncode}")
|
| 82 |
logger.error(f"FFmpeg command: {cmd}")
|
| 83 |
logger.error(f"FFmpeg stderr: {e.stderr}")
|
| 84 |
+
raise VideoToolError(f"Failed to create transition video. Details: {e.stderr}")
|
| 85 |
|
| 86 |
return output_path
|
| 87 |
|
|
|
|
| 93 |
video_paths (List[str]): A list of absolute paths to the video clips to be concatenated.
|
| 94 |
output_path (str): The absolute path for the final output video.
|
| 95 |
workspace_dir (str): The directory to use for temporary files, like the concat list.
|
| 96 |
+
|
| 97 |
+
Raises:
|
| 98 |
+
VideoToolError: If no video paths are provided or if the FFmpeg command fails.
|
| 99 |
"""
|
| 100 |
if not video_paths:
|
| 101 |
+
raise VideoToolError("VideoEncodeTool: No video fragments provided for concatenation.")
|
| 102 |
|
| 103 |
if len(video_paths) == 1:
|
| 104 |
logger.info("Only one video clip found. Skipping concatenation and just copying the file.")
|
|
|
|
| 105 |
shutil.copy(video_paths[0], output_path)
|
| 106 |
return
|
| 107 |
|
|
|
|
| 123 |
except subprocess.CalledProcessError as e:
|
| 124 |
logger.error(f"FFmpeg concatenation failed. Return code: {e.returncode}")
|
| 125 |
logger.error(f"FFmpeg stderr: {e.stderr}")
|
| 126 |
+
raise VideoToolError(f"Failed to assemble the final video using FFmpeg. Details: {e.stderr}")
|
| 127 |
except Exception as e:
|
| 128 |
logger.error(f"An unexpected error occurred during video concatenation: {e}", exc_info=True)
|
| 129 |
+
raise VideoToolError("An unexpected error occurred during the final video assembly.")
|
| 130 |
finally:
|
| 131 |
if os.path.exists(list_file_path):
|
| 132 |
os.remove(list_file_path)
|
| 133 |
|
| 134 |
|
| 135 |
# --- Singleton Instance ---
|
| 136 |
+
# We create a single instance of the tool to be imported by other modules.
|
| 137 |
+
video_encode_tool_singleton = VideoEncodeTool()
|