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.