Better logic for M4B generation that makes it very fast

This commit is contained in:
Deniz Şafak
2025-07-16 05:15:08 +03:00
parent 970fc6c9c0
commit d16c7603ce
2 changed files with 37 additions and 58 deletions
+1
View File
@@ -1,4 +1,5 @@
# 1.1.3 (pre-release) # 1.1.3 (pre-release)
- `M4B (with chapters)` generation is not faster now, as it directly generates `m4b` files instead of converting from `wav`, which significantly reduces processing time.
- Better sleep state handling for Linux. - Better sleep state handling for Linux.
- Fixed issue where the app would not restart properly on Windows. - Fixed issue where the app would not restart properly on Windows.
- Fixed last sentence/subtitle entry timing in generated subtitles, the end time of the final subtitle entry now correctly matches the end of the audio chunk, preventing zero or invalid timings at the end. - Fixed last sentence/subtitle entry timing in generated subtitles, the end time of the final subtitle entry now correctly matches the end of the audio chunk, preventing zero or invalid timings at the end.
+36 -58
View File
@@ -488,13 +488,27 @@ class ConversionThread(QThread):
) )
ffmpeg_proc = None ffmpeg_proc = None
elif self.output_format == "m4b": elif self.output_format == "m4b":
temp_wav_path = os.path.join( # Real-time M4B generation using FFmpeg pipe
out_dir, f"temp_{base_name}{suffix}.wav" static_ffmpeg.add_paths()
) merged_out_file = None
merged_out_file = sf.SoundFile(
temp_wav_path, "w", samplerate=24000, channels=1, format="wav"
)
ffmpeg_proc = None ffmpeg_proc = None
# Prepare ffmpeg command for m4b output
metadata_options = self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
cmd = [
"ffmpeg",
"-y",
"-thread_queue_size", "32768",
"-f", "f32le",
"-ar", "24000",
"-ac", "1",
"-i", "pipe:0",
"-c:a", "aac",
"-q:a", "2",
"-movflags", "+faststart+use_metadata_tags",
]
cmd += metadata_options
cmd.append(merged_out_path)
ffmpeg_proc = create_process(cmd, stdin=subprocess.PIPE, text=False)
elif self.output_format == "opus": elif self.output_format == "opus":
static_ffmpeg.add_paths() static_ffmpeg.add_paths()
cmd = [ cmd = [
@@ -912,8 +926,9 @@ class ConversionThread(QThread):
if self.output_format in ["wav", "mp3", "flac"]: if self.output_format in ["wav", "mp3", "flac"]:
merged_out_file.close() merged_out_file.close()
elif self.output_format == "m4b": elif self.output_format == "m4b":
merged_out_file.close() ffmpeg_proc.stdin.close()
# Only generate chapters info if there are chapters ffmpeg_proc.wait()
# Add chapters via fast post-processing
if total_chapters > 1: if total_chapters > 1:
chapters_info_path = f"{base_filepath_no_ext}_chapters.txt" chapters_info_path = f"{base_filepath_no_ext}_chapters.txt"
with open(chapters_info_path, "w", encoding="utf-8") as f: with open(chapters_info_path, "w", encoding="utf-8") as f:
@@ -925,63 +940,26 @@ class ConversionThread(QThread):
f.write(f"START={int(chapter['start']*1000)}\n") f.write(f"START={int(chapter['start']*1000)}\n")
f.write(f"END={int(chapter['end']*1000)}\n") f.write(f"END={int(chapter['end']*1000)}\n")
f.write(f"title={chapter_title}\n\n") f.write(f"title={chapter_title}\n\n")
metadata_options = ( # Fast mux chapters into m4b (write to temp file, then replace original)
self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
)
static_ffmpeg.add_paths() static_ffmpeg.add_paths()
orig_path = merged_out_path
root, ext = os.path.splitext(orig_path)
tmp_path = root + ".tmp" + ext
cmd = [ cmd = [
"ffmpeg", "ffmpeg",
"-y", "-y",
"-i", "-i", orig_path,
temp_wav_path, "-i", chapters_info_path,
"-i", "-map", "0:a",
chapters_info_path, "-map_metadata", "1",
"-map", "-map_chapters", "1",
"0:a", "-c:a", "copy",
"-map_metadata", tmp_path
"1",
"-map_chapters",
"1",
*metadata_options,
"-c:a",
"aac",
"-q:a",
"2",
"-movflags",
"+faststart+use_metadata_tags",
merged_out_path,
] ]
proc = create_process(cmd) proc = create_process(cmd)
proc.wait() proc.wait()
self.log_updated.emit( os.replace(tmp_path, orig_path)
("\nCleaning up temporary files...", "grey") os.remove(chapters_info_path)
)
if os.path.exists(chapters_info_path):
os.remove(chapters_info_path)
else:
# No chapters: skip chapters info and related ffmpeg args
metadata_options = (
self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
)
static_ffmpeg.add_paths()
cmd = [
"ffmpeg",
"-y",
"-i",
temp_wav_path,
*metadata_options,
"-c:a",
"aac",
"-q:a",
"2",
"-movflags",
"+faststart+use_metadata_tags",
merged_out_path,
]
proc = create_process(cmd)
proc.wait()
if os.path.exists(temp_wav_path):
os.remove(temp_wav_path)
elif self.output_format in ["opus"]: elif self.output_format in ["opus"]:
ffmpeg_proc.stdin.close() ffmpeg_proc.stdin.close()
ffmpeg_proc.wait() ffmpeg_proc.wait()