mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 05:40:26 +02:00
standardize m4b encoding to VBR -q:a 2; replace remaining ffmpeg blocks in desktop GUI with domain modules
This commit is contained in:
@@ -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
@@ -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(
|
|
||||||
[
|
|
||||||
"-c:a",
|
|
||||||
"aac",
|
|
||||||
"-q:a",
|
|
||||||
"2",
|
|
||||||
"-movflags",
|
|
||||||
"+faststart+use_metadata_tags",
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
cmd += metadata_options
|
# Insert thread queue size after ffmpeg header
|
||||||
cmd.append(merged_out_path)
|
cmd.insert(2, "-thread_queue_size")
|
||||||
|
cmd.insert(3, "32768")
|
||||||
|
if self.output_format == "m4b" and cover_path and os.path.exists(cover_path):
|
||||||
|
# Insert cover image input before the output path
|
||||||
|
output_path = cmd.pop()
|
||||||
|
cmd.extend([
|
||||||
|
"-i", cover_path,
|
||||||
|
"-map", "0:a",
|
||||||
|
"-map", "1",
|
||||||
|
"-c:v", "copy",
|
||||||
|
"-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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user