standardize m4b encoding to VBR -q:a 2; replace remaining ffmpeg blocks in desktop GUI with domain modules

This commit is contained in:
Artem Akymenko
2026-07-15 13:28:21 +00:00
parent ae9ab70421
commit 50d75eb2fc
3 changed files with 58 additions and 117 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ def build_ffmpeg_command(path: Path, fmt: str, metadata: Optional[Dict[str, str]
elif fmt == "opus": elif fmt == "opus":
base += ["-c:a", "libopus", "-b:a", "24000"] base += ["-c:a", "libopus", "-b:a", "24000"]
elif fmt == "m4b": elif fmt == "m4b":
base += ["-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart+use_metadata_tags"] base += ["-c:a", "aac", "-q:a", "2", "-movflags", "+faststart+use_metadata_tags"]
else: else:
base += ["-c:a", "copy"] base += ["-c:a", "copy"]
+56 -116
View File
@@ -23,6 +23,12 @@ from abogen.constants import (
from abogen.voice_formulas import get_new_voice from abogen.voice_formulas import get_new_voice
from abogen.infrastructure.subtitle_writer import _format_timestamp from abogen.infrastructure.subtitle_writer import _format_timestamp
from abogen.domain.split_pattern import get_split_pattern from abogen.domain.split_pattern import get_split_pattern
from abogen.domain.output_paths import (
resolve_output_directory,
build_output_path,
sanitize_output_stem,
)
from abogen.domain.audio_helpers import build_ffmpeg_command, to_float32
import abogen.hf_tracker as hf_tracker import abogen.hf_tracker as hf_tracker
import static_ffmpeg import static_ffmpeg
import threading # for efficient waiting import threading # for efficient waiting
@@ -639,15 +645,17 @@ class ConversionThread(QThread):
base_path = self.display_path if self.display_path else self.file_name base_path = self.display_path if self.display_path else self.file_name
base_name = os.path.splitext(os.path.basename(base_path))[0] base_name = os.path.splitext(os.path.basename(base_path))[0]
# Sanitize base_name for folder/file creation based on OS
sanitized_base_name = sanitize_name_for_os(base_name, is_folder=True) sanitized_base_name = sanitize_name_for_os(base_name, is_folder=True)
if self.save_option == "Save to Desktop": parent_dir = resolve_output_directory(
parent_dir = user_desktop_dir() save_mode=self.save_option,
elif self.save_option == "Save next to input file": stored_path=Path(base_path),
parent_dir = os.path.dirname(base_path) output_folder=getattr(self, "output_folder", None),
else: desktop_dir=Path(user_desktop_dir()),
parent_dir = self.output_folder or os.getcwd() user_output_path=None,
user_cache_outputs=Path(os.getcwd()),
)
parent_dir = str(parent_dir)
# Ensure the output folder exists, error if it doesn't # Ensure the output folder exists, error if it doesn't
if not os.path.exists(parent_dir): if not os.path.exists(parent_dir):
self.log_updated.emit( self.log_updated.emit(
@@ -717,77 +725,40 @@ class ConversionThread(QThread):
format=self.output_format, format=self.output_format,
) )
ffmpeg_proc = None ffmpeg_proc = None
elif self.output_format == "m4b": elif self.output_format in ("m4b", "opus"):
# Real-time M4B generation using FFmpeg pipe # Real-time generation using FFmpeg pipe
static_ffmpeg.add_paths() static_ffmpeg.add_paths()
merged_out_file = None merged_out_file = None
ffmpeg_proc = None ffmpeg_proc = None
metadata_options, cover_path = ( metadata_options, cover_path = (
self._extract_and_add_metadata_tags_to_ffmpeg_cmd() self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
if self.output_format == "m4b"
else ([], None)
) )
# Prepare ffmpeg command for m4b output cmd = build_ffmpeg_command(
cmd = [ Path(merged_out_path),
"ffmpeg", self.output_format,
"-y",
"-thread_queue_size",
"32768",
"-f",
"f32le",
"-ar",
"24000",
"-ac",
"1",
"-i",
"pipe:0",
]
if cover_path and os.path.exists(cover_path):
cmd.extend(
[
"-i",
cover_path,
"-map",
"0:a",
"-map",
"1",
"-c:v",
"copy",
"-disposition:v",
"attached_pic",
]
) )
cmd.extend( # Insert thread queue size after ffmpeg header
[ cmd.insert(2, "-thread_queue_size")
"-c:a", cmd.insert(3, "32768")
"aac", if self.output_format == "m4b" and cover_path and os.path.exists(cover_path):
"-q:a", # Insert cover image input before the output path
"2", output_path = cmd.pop()
"-movflags", cmd.extend([
"+faststart+use_metadata_tags", "-i", cover_path,
] "-map", "0:a",
) "-map", "1",
cmd += metadata_options "-c:v", "copy",
cmd.append(merged_out_path) "-disposition:v", "attached_pic",
])
cmd.extend(metadata_options)
cmd.append(output_path)
elif self.output_format == "m4b":
output_path = cmd.pop()
cmd.extend(metadata_options)
cmd.append(output_path)
ffmpeg_proc = create_process(cmd, stdin=subprocess.PIPE, text=False) ffmpeg_proc = create_process(cmd, stdin=subprocess.PIPE, text=False)
elif self.output_format == "opus":
static_ffmpeg.add_paths()
cmd = [
"ffmpeg",
"-y",
"-thread_queue_size",
"32768",
"-f",
"f32le",
"-ar",
"24000",
"-ac",
"1",
"-i",
"pipe:0",
]
cmd.extend(["-c:a", "libopus", "-b:a", "24000"])
cmd.append(merged_out_path)
ffmpeg_proc = create_process(cmd, stdin=subprocess.PIPE, text=False)
merged_out_file = None
else: else:
self.log_updated.emit( self.log_updated.emit(
(f"Unsupported output format: {self.output_format}", "red") (f"Unsupported output format: {self.output_format}", "red")
@@ -1560,58 +1531,27 @@ class ConversionThread(QThread):
) )
else: else:
static_ffmpeg.add_paths() static_ffmpeg.add_paths()
cmd = [ cmd = build_ffmpeg_command(
"ffmpeg", Path(merged_out_path),
"-y", self.output_format,
"-thread_queue_size", )
"32768", cmd.insert(2, "-thread_queue_size")
"-f", cmd.insert(3, "32768")
"f32le",
"-ar",
str(rate),
"-ac",
"1",
"-i",
"pipe:0",
]
if self.output_format == "m4b": if self.output_format == "m4b":
metadata_options, cover_path = ( metadata_options, cover_path = (
self._extract_and_add_metadata_tags_to_ffmpeg_cmd() self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
) )
if cover_path and os.path.exists(cover_path): if cover_path and os.path.exists(cover_path):
cmd.extend( output_path = cmd.pop()
[ cmd.extend([
"-i", "-i", cover_path,
cover_path, "-map", "0:a",
"-map", "-map", "1",
"0:a", "-c:v", "copy",
"-map", "-disposition:v", "attached_pic",
"1", ])
"-c:v", cmd.append(output_path)
"copy",
"-disposition:v",
"attached_pic",
]
)
cmd.extend(
[
"-c:a",
"aac",
"-q:a",
"2",
"-movflags",
"+faststart+use_metadata_tags",
]
)
cmd.extend(metadata_options) cmd.extend(metadata_options)
elif self.output_format == "opus":
cmd.extend(["-c:a", "libopus", "-b:a", "24000"])
else:
self.log_updated.emit(
(f"Unsupported output format: {self.output_format}", "red")
)
return
cmd.append(merged_out_path)
ffmpeg_proc = create_process(cmd, stdin=subprocess.PIPE, text=False) ffmpeg_proc = create_process(cmd, stdin=subprocess.PIPE, text=False)
# Always generate subtitles for subtitle input files # Always generate subtitles for subtitle input files
+1
View File
@@ -46,6 +46,7 @@ class TestBuildFfmpegCommand:
cmd = build_ffmpeg_command(Path("/out.m4b"), "m4b") cmd = build_ffmpeg_command(Path("/out.m4b"), "m4b")
assert "aac" in cmd assert "aac" in cmd
assert "-q:a" in cmd
assert "+faststart+use_metadata_tags" in cmd assert "+faststart+use_metadata_tags" in cmd
def test_wav_copy_codec(self): def test_wav_copy_codec(self):