feat: audio_edit_filters helper + optional -af chain on build_audio_clip_command
This commit is contained in:
+28
-2
@@ -201,12 +201,37 @@ def probe_duration(path: str) -> float | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def audio_edit_filters(duration: float, fade_in: float = 0.0,
|
||||||
|
fade_out: float = 0.0, normalize: bool = False,
|
||||||
|
gain_db: float = 0.0) -> list[str]:
|
||||||
|
"""Compose an ffmpeg -af chain for the non-destructive audio edits.
|
||||||
|
|
||||||
|
Trim is handled by the caller (it adjusts start/duration), so only
|
||||||
|
fade / normalize / gain appear here. Returns [] when nothing is set, so
|
||||||
|
the extract command stays byte-identical to the un-edited case."""
|
||||||
|
chain: list[str] = []
|
||||||
|
if fade_in > 0:
|
||||||
|
chain.append(f"afade=t=in:st=0:d={fade_in}")
|
||||||
|
if fade_out > 0:
|
||||||
|
st = round(max(0.0, duration - fade_out), 3)
|
||||||
|
chain.append(f"afade=t=out:st={st}:d={fade_out}")
|
||||||
|
if normalize:
|
||||||
|
# single-pass loudnorm (EBU R128 default target); fine for quick normalize
|
||||||
|
chain.append("loudnorm")
|
||||||
|
if gain_db != 0.0:
|
||||||
|
chain.append(f"volume={gain_db}dB")
|
||||||
|
return chain
|
||||||
|
|
||||||
|
|
||||||
def build_audio_clip_command(input_path: str, start: float, duration: float,
|
def build_audio_clip_command(input_path: str, start: float, duration: float,
|
||||||
out_path: str) -> list[str]:
|
out_path: str,
|
||||||
|
filters: list[str] | None = None) -> list[str]:
|
||||||
"""ffmpeg command to extract exactly *duration* seconds of audio starting
|
"""ffmpeg command to extract exactly *duration* seconds of audio starting
|
||||||
at *start*, re-encoded per *out_path*'s extension (wav/mp3/flac/…)."""
|
at *start*, re-encoded per *out_path*'s extension (wav/mp3/flac/…).
|
||||||
|
*filters* (if any) are joined into a single -af chain."""
|
||||||
ext = os.path.splitext(out_path)[1].lower()
|
ext = os.path.splitext(out_path)[1].lower()
|
||||||
codec = _AUDIO_CODEC_BY_EXT.get(ext, [])
|
codec = _AUDIO_CODEC_BY_EXT.get(ext, [])
|
||||||
|
af = ["-af", ",".join(filters)] if filters else []
|
||||||
return [
|
return [
|
||||||
_bin("ffmpeg"), "-y",
|
_bin("ffmpeg"), "-y",
|
||||||
"-ss", str(start),
|
"-ss", str(start),
|
||||||
@@ -214,6 +239,7 @@ def build_audio_clip_command(input_path: str, start: float, duration: float,
|
|||||||
"-t", str(duration),
|
"-t", str(duration),
|
||||||
"-vn",
|
"-vn",
|
||||||
*codec,
|
*codec,
|
||||||
|
*af,
|
||||||
out_path,
|
out_path,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,31 @@ def test_audio_clip_command_extension_case_insensitive():
|
|||||||
assert "flac" in build_audio_clip_command("/in.mp4", 0, 1, "/o/A.FLAC")
|
assert "flac" in build_audio_clip_command("/in.mp4", 0, 1, "/o/A.FLAC")
|
||||||
|
|
||||||
|
|
||||||
|
def test_audio_edit_filters_empty_when_defaults():
|
||||||
|
from core.ffmpeg import audio_edit_filters
|
||||||
|
assert audio_edit_filters(duration=3.0) == []
|
||||||
|
|
||||||
|
def test_audio_edit_filters_fade_normalize_gain():
|
||||||
|
from core.ffmpeg import audio_edit_filters
|
||||||
|
f = audio_edit_filters(duration=10.0, fade_in=0.5, fade_out=2.0,
|
||||||
|
normalize=True, gain_db=-3.0)
|
||||||
|
assert "afade=t=in:st=0:d=0.5" in f
|
||||||
|
assert "afade=t=out:st=8.0:d=2.0" in f # fade-out starts at duration - fade_out
|
||||||
|
assert "loudnorm" in f
|
||||||
|
assert "volume=-3.0dB" in f
|
||||||
|
|
||||||
|
def test_audio_clip_command_no_filters_unchanged():
|
||||||
|
cmd = build_audio_clip_command("/in.mp4", 1.0, 2.0, "/o/a.wav")
|
||||||
|
assert "-af" not in cmd
|
||||||
|
|
||||||
|
def test_audio_clip_command_appends_filter_chain():
|
||||||
|
cmd = build_audio_clip_command("/in.mp4", 1.0, 2.0, "/o/a.wav",
|
||||||
|
filters=["afade=t=in:st=0:d=0.5", "loudnorm"])
|
||||||
|
i = cmd.index("-af")
|
||||||
|
assert cmd[i + 1] == "afade=t=in:st=0:d=0.5,loudnorm"
|
||||||
|
assert i < len(cmd) - 1 and cmd[-1] == "/o/a.wav"
|
||||||
|
|
||||||
|
|
||||||
# --- ProcessedDB ---
|
# --- ProcessedDB ---
|
||||||
|
|
||||||
def test_db_add_and_get_markers():
|
def test_db_add_and_get_markers():
|
||||||
|
|||||||
Reference in New Issue
Block a user