Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -11,7 +11,7 @@ from moviepy.editor import (
|
|
| 11 |
AudioFileClip,
|
| 12 |
CompositeAudioClip,
|
| 13 |
concatenate_audioclips,
|
| 14 |
-
vfx,
|
| 15 |
)
|
| 16 |
import io
|
| 17 |
from PIL import Image
|
|
@@ -25,20 +25,37 @@ from moviepy.config import change_settings
|
|
| 25 |
change_settings({"VERBOSE": True})
|
| 26 |
|
| 27 |
# Safe video clip loader
|
| 28 |
-
def safe_video_clip(path):
|
| 29 |
try:
|
| 30 |
clip = VideoFileClip(path)
|
| 31 |
if clip.reader is None or clip.duration is None or clip.duration <= 0:
|
| 32 |
clip.close()
|
| 33 |
raise ValueError(f"Invalid or empty video file: {os.path.basename(path)}")
|
| 34 |
-
logger.debug(f"Loaded video: {path}, duration: {clip.duration}s")
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
try:
|
| 37 |
-
clip = clip.resize((
|
|
|
|
| 38 |
except Exception as e:
|
| 39 |
logger.warning(f"Failed to resize video {path}: {e}")
|
| 40 |
-
# Continue without resizing if it fails
|
| 41 |
-
|
|
|
|
| 42 |
except Exception as e:
|
| 43 |
raise ValueError(f"Failed to load video file '{os.path.basename(path)}': {str(e)}")
|
| 44 |
|
|
@@ -54,14 +71,17 @@ def safe_audio_clip(path):
|
|
| 54 |
except Exception as e:
|
| 55 |
raise ValueError(f"Failed to load audio file '{os.path.basename(path)}': {str(e)}")
|
| 56 |
|
| 57 |
-
def merge_videos_and_audios(video_paths, audio_paths, output_path, temp_dir, orig_vol=1.0, music_vol=0.5, audio_mode="
|
| 58 |
logger.debug(f"Merging {len(video_paths)} videos and {len(audio_paths)} audios to {output_path}")
|
| 59 |
|
| 60 |
# Load video clips
|
| 61 |
video_clips = []
|
|
|
|
| 62 |
for vp in video_paths:
|
| 63 |
try:
|
| 64 |
-
clip = safe_video_clip(vp)
|
|
|
|
|
|
|
| 65 |
video_clips.append(clip)
|
| 66 |
except Exception as e:
|
| 67 |
logger.warning(f"Skipping invalid video file {vp}: {e}")
|
|
@@ -81,7 +101,7 @@ def merge_videos_and_audios(video_paths, audio_paths, output_path, temp_dir, ori
|
|
| 81 |
|
| 82 |
# Concatenate video clips
|
| 83 |
final_video = concatenate_videoclips(video_clips, method="compose")
|
| 84 |
-
logger.debug(f"Concatenated video duration: {final_video.duration}s")
|
| 85 |
|
| 86 |
# Handle original video audio
|
| 87 |
original_audio = None
|
|
@@ -103,6 +123,9 @@ def merge_videos_and_audios(video_paths, audio_paths, output_path, temp_dir, ori
|
|
| 103 |
if final_audio and final_audio.duration < final_video.duration:
|
| 104 |
logger.debug(f"Looping audio to match video duration: {final_video.duration}s")
|
| 105 |
final_audio = final_audio.fx(vfx.loop, duration=final_video.duration)
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
if original_audio:
|
| 108 |
final_audio = CompositeAudioClip([final_audio, original_audio])
|
|
@@ -148,7 +171,7 @@ async def merge_endpoint(
|
|
| 148 |
files: list[UploadFile] = File(...),
|
| 149 |
orig_vol: float = Form(1.0),
|
| 150 |
music_vol: float = Form(0.5),
|
| 151 |
-
audio_mode: str = Form("
|
| 152 |
background_tasks: BackgroundTasks = BackgroundTasks(),
|
| 153 |
):
|
| 154 |
temp_dir = tempfile.mkdtemp()
|
|
|
|
| 11 |
AudioFileClip,
|
| 12 |
CompositeAudioClip,
|
| 13 |
concatenate_audioclips,
|
| 14 |
+
vfx,
|
| 15 |
)
|
| 16 |
import io
|
| 17 |
from PIL import Image
|
|
|
|
| 25 |
change_settings({"VERBOSE": True})
|
| 26 |
|
| 27 |
# Safe video clip loader
|
| 28 |
+
def safe_video_clip(path, target_width=1280, reference_aspect_ratio=None):
|
| 29 |
try:
|
| 30 |
clip = VideoFileClip(path)
|
| 31 |
if clip.reader is None or clip.duration is None or clip.duration <= 0:
|
| 32 |
clip.close()
|
| 33 |
raise ValueError(f"Invalid or empty video file: {os.path.basename(path)}")
|
| 34 |
+
logger.debug(f"Loaded video: {path}, duration: {clip.duration}s, resolution: {clip.size}, aspect ratio: {clip.w / clip.h}")
|
| 35 |
+
|
| 36 |
+
# Get video aspect ratio
|
| 37 |
+
current_aspect_ratio = clip.w / clip.h
|
| 38 |
+
|
| 39 |
+
# Use reference aspect ratio if provided, else use current video's aspect ratio
|
| 40 |
+
target_aspect_ratio = reference_aspect_ratio if reference_aspect_ratio else current_aspect_ratio
|
| 41 |
+
|
| 42 |
+
# Check if aspect ratios are compatible (within a small tolerance)
|
| 43 |
+
if reference_aspect_ratio and abs(current_aspect_ratio - target_aspect_ratio) > 0.05:
|
| 44 |
+
logger.warning(f"Video {path} has different aspect ratio ({current_aspect_ratio:.2f}) than reference ({target_aspect_ratio:.2f}). Adjusting to match.")
|
| 45 |
+
|
| 46 |
+
# Calculate target resolution to preserve aspect ratio
|
| 47 |
+
target_height = int(target_width / target_aspect_ratio)
|
| 48 |
+
# Ensure height is even (required by libx264 codec)
|
| 49 |
+
target_height = target_height if target_height % 2 == 0 else target_height + 1
|
| 50 |
+
|
| 51 |
try:
|
| 52 |
+
clip = clip.resize((target_width, target_height))
|
| 53 |
+
logger.debug(f"Resized video {path} to {target_width}x{target_height}, aspect ratio: {target_width / target_height}")
|
| 54 |
except Exception as e:
|
| 55 |
logger.warning(f"Failed to resize video {path}: {e}")
|
| 56 |
+
# Continue without resizing if it fails (may cause concatenation issues)
|
| 57 |
+
|
| 58 |
+
return clip, target_aspect_ratio
|
| 59 |
except Exception as e:
|
| 60 |
raise ValueError(f"Failed to load video file '{os.path.basename(path)}': {str(e)}")
|
| 61 |
|
|
|
|
| 71 |
except Exception as e:
|
| 72 |
raise ValueError(f"Failed to load audio file '{os.path.basename(path)}': {str(e)}")
|
| 73 |
|
| 74 |
+
def merge_videos_and_audios(video_paths, audio_paths, output_path, temp_dir, orig_vol=1.0, music_vol=0.5, audio_mode="concatenate"):
|
| 75 |
logger.debug(f"Merging {len(video_paths)} videos and {len(audio_paths)} audios to {output_path}")
|
| 76 |
|
| 77 |
# Load video clips
|
| 78 |
video_clips = []
|
| 79 |
+
reference_aspect_ratio = None
|
| 80 |
for vp in video_paths:
|
| 81 |
try:
|
| 82 |
+
clip, aspect_ratio = safe_video_clip(vp, reference_aspect_ratio=reference_aspect_ratio)
|
| 83 |
+
if reference_aspect_ratio is None:
|
| 84 |
+
reference_aspect_ratio = aspect_ratio # Set reference from first valid video
|
| 85 |
video_clips.append(clip)
|
| 86 |
except Exception as e:
|
| 87 |
logger.warning(f"Skipping invalid video file {vp}: {e}")
|
|
|
|
| 101 |
|
| 102 |
# Concatenate video clips
|
| 103 |
final_video = concatenate_videoclips(video_clips, method="compose")
|
| 104 |
+
logger.debug(f"Concatenated video duration: {final_video.duration}s, resolution: {final_video.size}")
|
| 105 |
|
| 106 |
# Handle original video audio
|
| 107 |
original_audio = None
|
|
|
|
| 123 |
if final_audio and final_audio.duration < final_video.duration:
|
| 124 |
logger.debug(f"Looping audio to match video duration: {final_video.duration}s")
|
| 125 |
final_audio = final_audio.fx(vfx.loop, duration=final_video.duration)
|
| 126 |
+
elif final_audio and final_audio.duration > final_video.duration:
|
| 127 |
+
logger.debug(f"Trimming audio to match video duration: {final_video.duration}s")
|
| 128 |
+
final_audio = final_audio.subclip(0, final_video.duration)
|
| 129 |
|
| 130 |
if original_audio:
|
| 131 |
final_audio = CompositeAudioClip([final_audio, original_audio])
|
|
|
|
| 171 |
files: list[UploadFile] = File(...),
|
| 172 |
orig_vol: float = Form(1.0),
|
| 173 |
music_vol: float = Form(0.5),
|
| 174 |
+
audio_mode: str = Form("concatenate"),
|
| 175 |
background_tasks: BackgroundTasks = BackgroundTasks(),
|
| 176 |
):
|
| 177 |
temp_dir = tempfile.mkdtemp()
|