Spaces:
Running
Running
from pydub import AudioSegment | |
import os | |
def merge_mp3_files(file_paths, output_filename, pause_ms=500): | |
""" | |
Merges multiple MP3 files into a single MP3 file with a specified pause | |
between each segment. Skips missing or empty files. | |
Args: | |
file_paths (list): A list of paths to MP3 files to merge. | |
Can contain None entries for files that failed synthesis; these will be skipped. | |
output_filename (str): The path to save the merged MP3 file. | |
pause_ms (int): Duration of silence in milliseconds to add between segments. | |
Returns: | |
str: The path to the merged MP3 file if successful, None otherwise. | |
""" | |
if not file_paths: | |
print("Warning: No file paths provided for merging.") | |
return None | |
valid_segments = [] | |
for file_path in file_paths: | |
if file_path and os.path.exists(file_path) and os.path.getsize(file_path) > 0: | |
try: | |
segment = AudioSegment.from_mp3(file_path) | |
valid_segments.append(segment) | |
except Exception as e: | |
print(f"Error loading audio segment from {file_path}: {e}. Skipping this file.") | |
elif file_path: # File path provided but file is missing or empty | |
print(f"Warning: File {file_path} is missing or empty. Skipping.") | |
# If file_path is None, it's silently skipped (already handled upstream) | |
if not valid_segments: | |
print("No valid audio segments found to merge.") | |
return None | |
# Start with the first valid segment | |
combined_audio = valid_segments[0] | |
# Add subsequent segments with pauses | |
if len(valid_segments) > 1: | |
pause_segment = AudioSegment.silent(duration=max(0, pause_ms)) # Ensure pause_ms is not negative | |
for segment in valid_segments[1:]: | |
combined_audio += pause_segment | |
combined_audio += segment | |
try: | |
# Export the combined audio to MP3 format | |
# May require ffmpeg/libav to be installed and accessible in PATH | |
combined_audio.export(output_filename, format="mp3") | |
if os.path.exists(output_filename) and os.path.getsize(output_filename) > 0: | |
return output_filename | |
else: | |
print(f"Error: Merged file {output_filename} was not created or is empty after export.") | |
return None | |
except Exception as e: | |
print(f"Error exporting merged MP3 to {output_filename}: {e}") | |
return None | |
# Helper function to create dummy MP3 files for testing (requires pydub and ffmpeg) | |
def _create_dummy_mp3(filename, duration_ms=1000, text_for_log="dummy"): | |
try: | |
# Create a silent audio segment | |
silence = AudioSegment.silent(duration=duration_ms) | |
# Export it as an MP3 file | |
silence.export(filename, format="mp3") | |
print(f"Successfully created dummy MP3: {filename} (duration: {duration_ms}ms) for '{text_for_log}'") | |
return True | |
except Exception as e: | |
print(f"Could not create dummy MP3 '{filename}'. Ensure ffmpeg is installed and accessible. Error: {e}") | |
return False | |
if __name__ == '__main__': | |
print("--- Testing merge_mp3_files ---") | |
test_output_dir = "test_audio_merge_output" | |
os.makedirs(test_output_dir, exist_ok=True) | |
dummy_files = [] | |
# Create some dummy MP3 files for the test | |
if _create_dummy_mp3(os.path.join(test_output_dir, "dummy1.mp3"), 1000, "Segment 1"): | |
dummy_files.append(os.path.join(test_output_dir, "dummy1.mp3")) | |
if _create_dummy_mp3(os.path.join(test_output_dir, "dummy2.mp3"), 1500, "Segment 2"): | |
dummy_files.append(os.path.join(test_output_dir, "dummy2.mp3")) | |
# Test case 1: Merge existing files | |
if len(dummy_files) == 2: | |
output_merged_1 = os.path.join(test_output_dir, "merged_test1.mp3") | |
print(f"\nAttempting to merge: {dummy_files} with 300ms pause into {output_merged_1}") | |
result_path_1 = merge_mp3_files(dummy_files, output_merged_1, pause_ms=300) | |
if result_path_1 and os.path.exists(result_path_1): | |
print(f"SUCCESS: Merged audio created at: {result_path_1} (Size: {os.path.getsize(result_path_1)} bytes)") | |
else: | |
print(f"FAILURE: Merging test case 1 failed.") | |
else: | |
print("\nSkipping merge test case 1 due to failure in creating dummy files.") | |
# Test case 2: Include a non-existent file and a None entry | |
files_with_issues = [ | |
dummy_files[0] if dummy_files else None, | |
os.path.join(test_output_dir, "non_existent_file.mp3"), | |
None, # Representing a failed synthesis | |
dummy_files[1] if len(dummy_files) > 1 else None | |
] | |
# Filter out None from the list if dummy files weren't created | |
files_with_issues_filtered = [f for f in files_with_issues if f is not None or isinstance(f, str)] | |
if any(f for f in files_with_issues_filtered if f and os.path.exists(f)): # Proceed if at least one valid dummy file exists | |
output_merged_2 = os.path.join(test_output_dir, "merged_test2_with_issues.mp3") | |
print(f"\nAttempting to merge: {files_with_issues_filtered} with 500ms pause into {output_merged_2}") | |
result_path_2 = merge_mp3_files(files_with_issues_filtered, output_merged_2, pause_ms=500) | |
if result_path_2 and os.path.exists(result_path_2): | |
print(f"SUCCESS (with skips): Merged audio created at: {result_path_2} (Size: {os.path.getsize(result_path_2)} bytes)") | |
else: | |
print(f"NOTE: Merging test case 2 might result in fewer segments or failure if no valid files remained.") | |
else: | |
print("\nSkipping merge test case 2 as no valid dummy files were available.") | |
# Test case 3: Empty list of files | |
output_merged_3 = os.path.join(test_output_dir, "merged_test3_empty.mp3") | |
print(f"\nAttempting to merge an empty list of files into {output_merged_3}") | |
result_path_3 = merge_mp3_files([], output_merged_3, pause_ms=100) | |
if result_path_3 is None: | |
print("SUCCESS: Correctly handled empty file list (returned None).") | |
else: | |
print(f"FAILURE: Expected None for empty file list, got {result_path_3}") | |
# Test case 4: List with only None or invalid paths | |
output_merged_4 = os.path.join(test_output_dir, "merged_test4_all_invalid.mp3") | |
print(f"\nAttempting to merge list with only invalid/None files into {output_merged_4}") | |
result_path_4 = merge_mp3_files([None, "non_existent.mp3"], output_merged_4, pause_ms=100) | |
if result_path_4 is None: | |
print("SUCCESS: Correctly handled list with only invalid/None files (returned None).") | |
else: | |
print(f"FAILURE: Expected None for all-invalid list, got {result_path_4}") | |
print(f"\nTest finished. Check ./{test_output_dir}/ for any generated files.") | |
# You might want to add shutil.rmtree(test_output_dir) here for cleanup after visual inspection. |