abocha commited on
Commit
b3059af
·
1 Parent(s): b65373e

no reencode in mix

Browse files
Files changed (1) hide show
  1. utils/merge_audio.py +78 -36
utils/merge_audio.py CHANGED
@@ -1,45 +1,87 @@
1
- from pydub import AudioSegment
 
 
2
  import os
3
 
4
  def merge_mp3_files(file_paths, output_filename, pause_ms=500):
 
 
 
 
 
5
  if not file_paths:
6
- print("Warning: No file paths provided for merging.")
7
  return None
8
 
9
- valid_segments = []
10
- for file_path in file_paths:
11
- if file_path and os.path.exists(file_path) and os.path.getsize(file_path) > 0:
12
- try:
13
- segment = AudioSegment.from_mp3(file_path)
14
- valid_segments.append(segment)
15
- except Exception as e:
16
- print(f"Error loading audio segment from {file_path}: {e}. Skipping this file.")
17
- elif file_path:
18
- print(f"Warning: File {file_path} is missing or empty. Skipping.")
19
-
20
- if not valid_segments:
21
- print("No valid audio segments found to merge.")
22
  return None
23
 
24
- combined_audio = valid_segments[0]
25
- if len(valid_segments) > 1:
26
- pause_segment = AudioSegment.silent(duration=max(0, pause_ms))
27
- for segment in valid_segments[1:]:
28
- combined_audio += pause_segment
29
- combined_audio += segment
30
-
31
- try:
32
- combined_audio.export(
33
- output_filename,
34
- format="mp3",
35
- bitrate="128k",
36
- parameters=["-ac", "2", "-ar", "44100"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  )
38
- if os.path.exists(output_filename) and os.path.getsize(output_filename) > 0:
39
- return output_filename
40
- else:
41
- print(f"Error: Merged file {output_filename} was not created or is empty after export.")
42
- return None
43
- except Exception as e:
44
- print(f"Error exporting merged MP3 to {output_filename}: {e}")
45
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import pathlib
3
+ import tempfile
4
  import os
5
 
6
  def merge_mp3_files(file_paths, output_filename, pause_ms=500):
7
+ """
8
+ Concatenate MP3s *without re‑encoding*.
9
+ Adds a silent gap (`pause_ms`) between clips by generating a
10
+ temporary silent MP3 at the same encoding params as the sources.
11
+ """
12
  if not file_paths:
13
+ print("Warning: no input files.")
14
  return None
15
 
16
+ # keep only files that exist and are non‑empty
17
+ valid = [p for p in file_paths if p and os.path.exists(p) and os.path.getsize(p) > 0]
18
+ if not valid:
19
+ print("No valid audio segments found.")
 
 
 
 
 
 
 
 
 
20
  return None
21
 
22
+ concat_list = []
23
+
24
+ # optional pause: create one silent mp3 we can reuse
25
+ if pause_ms > 0:
26
+ silent_path = _make_silent_mp3(duration_ms=pause_ms,
27
+ template_mp3=valid[0])
28
+ else:
29
+ silent_path = None
30
+
31
+ for i, p in enumerate(valid):
32
+ concat_list.append(p)
33
+ if silent_path and i < len(valid) - 1:
34
+ concat_list.append(silent_path)
35
+
36
+ # ffmpeg concat demuxer expects a text file listing the parts
37
+ with tempfile.NamedTemporaryFile("w+", delete=False) as tf:
38
+ for part in concat_list:
39
+ tf.write(f"file '{pathlib.Path(part).as_posix()}'\n")
40
+ tf.flush()
41
+
42
+ subprocess.run(
43
+ [
44
+ "ffmpeg", "-y",
45
+ "-f", "concat", "-safe", "0",
46
+ "-i", tf.name,
47
+ "-c", "copy", # ← no re‑encode!
48
+ output_filename
49
+ ],
50
+ check=True,
51
+ stdout=subprocess.DEVNULL,
52
+ stderr=subprocess.DEVNULL,
53
  )
54
+ return output_filename
55
+
56
+
57
+ def _make_silent_mp3(duration_ms: int, template_mp3: str) -> str:
58
+ """
59
+ Create a silent MP3 (CBR, same sample‑rate & channels as template)
60
+ so we can insert a pause without changing codecs later.
61
+ """
62
+ silent_path = tempfile.mktemp(suffix=".mp3")
63
+ # Extract params from the template
64
+ probe = subprocess.check_output(
65
+ ["ffprobe", "-v", "error", "-select_streams", "a:0",
66
+ "-show_entries", "stream=sample_rate,channels,bit_rate",
67
+ "-of", "default=nw=1:nk=1", template_mp3],
68
+ text=True
69
+ ).strip().splitlines()
70
+ sr, ch, br = probe # e.g. "44100", "2", "128000"
71
+
72
+ subprocess.run(
73
+ [
74
+ "ffmpeg", "-y",
75
+ "-f", "lavfi",
76
+ "-i", f"anullsrc=r={sr}:cl=mono",
77
+ "-t", str(duration_ms / 1000),
78
+ "-ac", ch,
79
+ "-ar", sr,
80
+ "-b:a", br,
81
+ silent_path
82
+ ],
83
+ check=True,
84
+ stdout=subprocess.DEVNULL,
85
+ stderr=subprocess.DEVNULL,
86
+ )
87
+ return silent_path