Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import tempfile
|
| 3 |
import os
|
|
|
|
| 4 |
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip, concatenate_audioclips
|
| 5 |
import numpy as np
|
| 6 |
import logging
|
|
@@ -26,6 +27,46 @@ def check_port(port):
|
|
| 26 |
except socket.error:
|
| 27 |
return False
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
def trim_silence(audio_clip, threshold=0.005):
|
| 30 |
"""
|
| 31 |
Trim silence from the start and end of an audio clip.
|
|
@@ -77,6 +118,7 @@ def merge_videos_and_audios(video_files=None, audio_files=None, orig_vol=1.0, mu
|
|
| 77 |
- If only video_files: Merge videos, retaining their original audio.
|
| 78 |
- If only audio_files: Merge audio files into a single audio file.
|
| 79 |
- If both: Merge videos and overlay the concatenated audio.
|
|
|
|
| 80 |
Args:
|
| 81 |
video_files: List of video file paths (optional)
|
| 82 |
audio_files: List of audio file paths (optional)
|
|
@@ -86,6 +128,14 @@ def merge_videos_and_audios(video_files=None, audio_files=None, orig_vol=1.0, mu
|
|
| 86 |
Path to the merged file (video or audio) or error message.
|
| 87 |
"""
|
| 88 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
# Ensure at least two files are provided (videos, audios, or combination)
|
| 90 |
video_count = len(video_files) if video_files else 0
|
| 91 |
audio_count = len(audio_files) if audio_files else 0
|
|
@@ -103,7 +153,8 @@ def merge_videos_and_audios(video_files=None, audio_files=None, orig_vol=1.0, mu
|
|
| 103 |
|
| 104 |
# Case 1: Audio only
|
| 105 |
if audio_count >= 2 and video_count == 0:
|
| 106 |
-
|
|
|
|
| 107 |
logger.info("Merging audio files only")
|
| 108 |
|
| 109 |
# Load, normalize, and trim audio clips
|
|
@@ -153,7 +204,11 @@ def merge_videos_and_audios(video_files=None, audio_files=None, orig_vol=1.0, mu
|
|
| 153 |
return output_path
|
| 154 |
|
| 155 |
# Case 2: Video only or Video with Audio
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
# Load and concatenate video clips
|
| 159 |
video_clips = [VideoFileClip(video) for video in video_files]
|
|
@@ -304,6 +359,7 @@ if __name__ == "__main__":
|
|
| 304 |
with gr.Blocks(title="Video and Audio Merger API") as app:
|
| 305 |
gr.Markdown("## Video and Audio Merger API")
|
| 306 |
gr.Markdown("Upload at least 2 files total (videos, audios, or a combination) to merge them. Videos and audios are optional.")
|
|
|
|
| 307 |
|
| 308 |
with gr.Row():
|
| 309 |
video_input = gr.File(label="Video Files (Optional, at least 2 if no audio)", type="filepath", file_count="multiple")
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import tempfile
|
| 3 |
import os
|
| 4 |
+
import re
|
| 5 |
from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, CompositeAudioClip, concatenate_audioclips
|
| 6 |
import numpy as np
|
| 7 |
import logging
|
|
|
|
| 27 |
except socket.error:
|
| 28 |
return False
|
| 29 |
|
| 30 |
+
def sort_files_by_index(file_list, prefix_pattern):
|
| 31 |
+
"""
|
| 32 |
+
Sort files based on their numerical index in the filename.
|
| 33 |
+
Args:
|
| 34 |
+
file_list: List of file paths (e.g., ['file2.mp4', 'file1.mp4', 'file3.mp4'])
|
| 35 |
+
prefix_pattern: Regex pattern to match the prefix and number (e.g., r'file(\d+)\.mp4')
|
| 36 |
+
Returns:
|
| 37 |
+
Sorted list of file paths
|
| 38 |
+
"""
|
| 39 |
+
if not file_list:
|
| 40 |
+
return []
|
| 41 |
+
|
| 42 |
+
def extract_index(filename):
|
| 43 |
+
match = re.match(prefix_pattern, os.path.basename(filename))
|
| 44 |
+
if match:
|
| 45 |
+
return int(match.group(1))
|
| 46 |
+
return float('inf') # Invalid filenames go to the end
|
| 47 |
+
|
| 48 |
+
sorted_files = sorted(file_list, key=extract_index)
|
| 49 |
+
logger.info(f"Sorted files: {sorted_files}")
|
| 50 |
+
return sorted_files
|
| 51 |
+
|
| 52 |
+
def get_indices_string(file_list, prefix_pattern):
|
| 53 |
+
"""
|
| 54 |
+
Extract the numerical indices from filenames and return as a string.
|
| 55 |
+
Args:
|
| 56 |
+
file_list: List of file paths
|
| 57 |
+
prefix_pattern: Regex pattern to match the prefix and number
|
| 58 |
+
Returns:
|
| 59 |
+
String of indices (e.g., '123' for ['file1.mp4', 'file2.mp4', 'file3.mp4'])
|
| 60 |
+
"""
|
| 61 |
+
if not file_list:
|
| 62 |
+
return ""
|
| 63 |
+
indices = []
|
| 64 |
+
for filename in file_list:
|
| 65 |
+
match = re.match(prefix_pattern, os.path.basename(filename))
|
| 66 |
+
if match:
|
| 67 |
+
indices.append(match.group(1))
|
| 68 |
+
return "".join(indices)
|
| 69 |
+
|
| 70 |
def trim_silence(audio_clip, threshold=0.005):
|
| 71 |
"""
|
| 72 |
Trim silence from the start and end of an audio clip.
|
|
|
|
| 118 |
- If only video_files: Merge videos, retaining their original audio.
|
| 119 |
- If only audio_files: Merge audio files into a single audio file.
|
| 120 |
- If both: Merge videos and overlay the concatenated audio.
|
| 121 |
+
Files are sorted by numerical index in their filenames (e.g., file1.mp4, file2.mp4).
|
| 122 |
Args:
|
| 123 |
video_files: List of video file paths (optional)
|
| 124 |
audio_files: List of audio file paths (optional)
|
|
|
|
| 128 |
Path to the merged file (video or audio) or error message.
|
| 129 |
"""
|
| 130 |
try:
|
| 131 |
+
# Sort files by numerical index
|
| 132 |
+
video_files = sort_files_by_index(video_files, r'file(\d+)\.mp4')
|
| 133 |
+
audio_files = sort_files_by_index(audio_files, r'audio(\d+)\.mp4')
|
| 134 |
+
|
| 135 |
+
# Get indices for output naming
|
| 136 |
+
video_indices = get_indices_string(video_files, r'file(\d+)\.mp4')
|
| 137 |
+
audio_indices = get_indices_string(audio_files, r'audio(\d+)\.mp4')
|
| 138 |
+
|
| 139 |
# Ensure at least two files are provided (videos, audios, or combination)
|
| 140 |
video_count = len(video_files) if video_files else 0
|
| 141 |
audio_count = len(audio_files) if audio_files else 0
|
|
|
|
| 153 |
|
| 154 |
# Case 1: Audio only
|
| 155 |
if audio_count >= 2 and video_count == 0:
|
| 156 |
+
output_filename = f"combined_audio_{audio_indices}.mp3"
|
| 157 |
+
output_path = os.path.join(temp_dir, output_filename)
|
| 158 |
logger.info("Merging audio files only")
|
| 159 |
|
| 160 |
# Load, normalize, and trim audio clips
|
|
|
|
| 204 |
return output_path
|
| 205 |
|
| 206 |
# Case 2: Video only or Video with Audio
|
| 207 |
+
if audio_indices:
|
| 208 |
+
output_filename = f"combined_video_{video_indices}_with_audio_{audio_indices}.mp4"
|
| 209 |
+
else:
|
| 210 |
+
output_filename = f"combined_video_{video_indices}.mp4"
|
| 211 |
+
output_path = os.path.join(temp_dir, output_filename)
|
| 212 |
|
| 213 |
# Load and concatenate video clips
|
| 214 |
video_clips = [VideoFileClip(video) for video in video_files]
|
|
|
|
| 359 |
with gr.Blocks(title="Video and Audio Merger API") as app:
|
| 360 |
gr.Markdown("## Video and Audio Merger API")
|
| 361 |
gr.Markdown("Upload at least 2 files total (videos, audios, or a combination) to merge them. Videos and audios are optional.")
|
| 362 |
+
gr.Markdown("For API usage, name videos as file1.mp4, file2.mp4, etc., and audios as audio1.mp4, audio2.mp4, etc.")
|
| 363 |
|
| 364 |
with gr.Row():
|
| 365 |
video_input = gr.File(label="Video Files (Optional, at least 2 if no audio)", type="filepath", file_count="multiple")
|