feat: more optional options
This commit is contained in:
@@ -157,8 +157,32 @@ python video-generator/scripts/combine_video_audio.py \
|
||||
--rate 190
|
||||
```
|
||||
|
||||
## Optional advanced options
|
||||
|
||||
These options are not needed for normal usage.
|
||||
|
||||
- `--audio-output "output/voiceover.wav"` — saves a copy of the generated voiceover WAV in one-step mode.
|
||||
- `--keep-temp` — keeps temporary voice parts for debugging. By default, temporary files are deleted.
|
||||
- `--volume 1.2` — changes voiceover volume. Default is `1.0`.
|
||||
- `--silence-padding 0.15` — adds a small pause after each caption if the SRT time slot has enough space. Default is `0.0`.
|
||||
- `--sample-rate 48000` — changes generated audio sample rate. Default is `44100`.
|
||||
- `--channels 1` — changes generated audio channels. Use `1` for mono or `2` for stereo. Default is `2`.
|
||||
- `--reencode-video` — re-encodes video with H.264 if the copied video output has compatibility issues.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
python video-generator/scripts/combine_video_audio.py \
|
||||
--video "video-generator/input/your_video.mp4" \
|
||||
--srt "video-generator/subtitles/your_subtitles.srt" \
|
||||
--output "video-generator/output/final_video.mp4" \
|
||||
--audio-output "video-generator/output/voiceover.wav" \
|
||||
--volume 1.2 \
|
||||
--silence-padding 0.15 \
|
||||
--sample-rate 48000 \
|
||||
--channels 2
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
If a caption has too much text for its time slot, the script keeps the SRT timing and trims the speech. Fix that by increasing the subtitle duration, shortening the sentence, or increasing speed.
|
||||
|
||||
If the final MP4 has compatibility issues, add `--reencode-video` to the combine command.
|
||||
@@ -72,12 +72,13 @@ def speak_macos(text: str, voice: str, rate: int, output: Path) -> None:
|
||||
run(["say", "-v", voice, "-r", str(rate), "-o", str(output), text])
|
||||
|
||||
|
||||
def speak_windows(text: str, voice: str, rate: int, output: Path) -> None:
|
||||
def speak_windows(text: str, voice: str, rate: int, volume: int, output: Path) -> None:
|
||||
ps_script = f"""
|
||||
Add-Type -AssemblyName System.Speech
|
||||
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
|
||||
$synth.SelectVoice({ps_quote(voice)})
|
||||
$synth.Rate = {rate}
|
||||
$synth.Volume = {volume}
|
||||
$synth.SetOutputToWaveFile({ps_quote(str(output))})
|
||||
$synth.Speak({ps_quote(text)})
|
||||
$synth.Dispose()
|
||||
@@ -101,18 +102,29 @@ def default_rate() -> int:
|
||||
raise RuntimeError("One-step SRT voiceover generation is supported only on macOS and Windows.")
|
||||
|
||||
|
||||
def speak(text: str, voice: str, rate: int, output: Path) -> None:
|
||||
def speak(text: str, voice: str, rate: int, volume: float, output: Path) -> None:
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
speak_macos(text, voice, rate, output)
|
||||
return
|
||||
if system == "Windows":
|
||||
speak_windows(text, voice, rate, output)
|
||||
windows_volume = max(0, min(100, round(volume * 100)))
|
||||
speak_windows(text, voice, rate, windows_volume, output)
|
||||
return
|
||||
raise RuntimeError("One-step SRT voiceover generation is supported only on macOS and Windows.")
|
||||
|
||||
|
||||
def create_voiceover_from_srt(srt_path: Path, wav_output: Path, voice: str, rate: int, work_dir: Path) -> None:
|
||||
def create_voiceover_from_srt(
|
||||
srt_path: Path,
|
||||
wav_output: Path,
|
||||
voice: str,
|
||||
rate: int,
|
||||
work_dir: Path,
|
||||
volume: float,
|
||||
silence_padding: float,
|
||||
sample_rate: int,
|
||||
channels: int,
|
||||
) -> None:
|
||||
captions = read_srt(srt_path)
|
||||
segment_files = []
|
||||
|
||||
@@ -121,18 +133,23 @@ def create_voiceover_from_srt(srt_path: Path, wav_output: Path, voice: str, rate
|
||||
raw_audio = work_dir / f"raw_{i:03d}.aiff" if platform.system() == "Darwin" else work_dir / f"raw_{i:03d}.wav"
|
||||
segment_wav = work_dir / f"seg_{i:03d}.wav"
|
||||
|
||||
speak(text, voice, rate, raw_audio)
|
||||
speak(text, voice, rate, volume, raw_audio)
|
||||
|
||||
spoken_duration = media_duration(raw_audio)
|
||||
padding = min(silence_padding, max(duration - spoken_duration, 0))
|
||||
effective_duration = duration if silence_padding == 0 else min(duration, spoken_duration + padding)
|
||||
|
||||
if spoken_duration > duration:
|
||||
print(f"Warning: caption {i} speech is {spoken_duration:.2f}s but slot is {duration:.2f}s; it will be trimmed.")
|
||||
|
||||
audio_filter = f"volume={volume},apad,atrim=0:{effective_duration}"
|
||||
|
||||
run([
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(raw_audio),
|
||||
"-af", f"apad,atrim=0:{duration}",
|
||||
"-ar", "44100",
|
||||
"-ac", "2",
|
||||
"-af", audio_filter,
|
||||
"-ar", str(sample_rate),
|
||||
"-ac", str(channels),
|
||||
str(segment_wav),
|
||||
])
|
||||
segment_files.append(segment_wav)
|
||||
@@ -142,7 +159,7 @@ def create_voiceover_from_srt(srt_path: Path, wav_output: Path, voice: str, rate
|
||||
run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_list), "-c:a", "pcm_s16le", str(wav_output)])
|
||||
|
||||
|
||||
def combine_video_audio(video_file: Path, audio_file: Path, output_file: Path, reencode_video: bool) -> None:
|
||||
def combine_video_audio(video_file: Path, audio_file: Path, output_file: Path, reencode_video: bool, volume: float) -> None:
|
||||
video_codec_args = ["-c:v", "libx264", "-preset", "medium", "-crf", "18"] if reencode_video else ["-c:v", "copy"]
|
||||
|
||||
command = [
|
||||
@@ -152,6 +169,7 @@ def combine_video_audio(video_file: Path, audio_file: Path, output_file: Path, r
|
||||
"-map", "0:v:0",
|
||||
"-map", "1:a:0",
|
||||
*video_codec_args,
|
||||
"-af", f"volume={volume}",
|
||||
"-c:a", "aac",
|
||||
"-b:a", "192k",
|
||||
"-movflags", "+faststart",
|
||||
@@ -170,6 +188,12 @@ def main() -> None:
|
||||
parser.add_argument("--output", required=True, help="Output MP4 file, for example output/final_video.mp4")
|
||||
parser.add_argument("--voice", default=None, help="Voice name for one-step SRT mode")
|
||||
parser.add_argument("--rate", type=int, default=None, help="Speech rate for one-step SRT mode")
|
||||
parser.add_argument("--audio-output", default=None, help="Optional copy of the generated voiceover WAV in one-step SRT mode")
|
||||
parser.add_argument("--keep-temp", action="store_true", help="Keep temporary voice parts for debugging in one-step SRT mode")
|
||||
parser.add_argument("--volume", type=float, default=1.0, help="Voiceover volume multiplier. Default: 1.0")
|
||||
parser.add_argument("--silence-padding", type=float, default=0.0, help="Seconds of silence after each caption in one-step SRT mode, if the SRT slot allows it. Default: 0.0")
|
||||
parser.add_argument("--sample-rate", type=int, default=44100, help="Generated voiceover sample rate in one-step SRT mode. Default: 44100")
|
||||
parser.add_argument("--channels", type=int, default=2, choices=[1, 2], help="Generated voiceover channels in one-step SRT mode. Default: 2")
|
||||
parser.add_argument(
|
||||
"--reencode-video",
|
||||
action="store_true",
|
||||
@@ -181,6 +205,10 @@ def main() -> None:
|
||||
parser.error("Provide either --audio for the current two-step flow or --srt for one-step generation.")
|
||||
if args.audio and args.srt:
|
||||
parser.error("Use either --audio or --srt, not both.")
|
||||
if args.volume <= 0:
|
||||
parser.error("--volume must be greater than 0.")
|
||||
if args.silence_padding < 0:
|
||||
parser.error("--silence-padding must be 0 or greater.")
|
||||
|
||||
video_file = resolve_path(args.video)
|
||||
output_file = resolve_path(args.output)
|
||||
@@ -194,7 +222,7 @@ def main() -> None:
|
||||
audio_file = resolve_path(args.audio)
|
||||
if not audio_file.exists():
|
||||
raise FileNotFoundError(f"Audio not found: {audio_file}")
|
||||
combine_video_audio(video_file, audio_file, output_file, args.reencode_video)
|
||||
combine_video_audio(video_file, audio_file, output_file, args.reencode_video, args.volume)
|
||||
else:
|
||||
srt_path = resolve_path(args.srt)
|
||||
if not srt_path.exists():
|
||||
@@ -202,14 +230,33 @@ def main() -> None:
|
||||
|
||||
voice = args.voice or default_voice()
|
||||
rate = args.rate if args.rate is not None else default_rate()
|
||||
audio_output = resolve_path(args.audio_output) if args.audio_output else None
|
||||
if audio_output:
|
||||
audio_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
temp_dir = Path(tempfile.mkdtemp(prefix="video_generator_"))
|
||||
try:
|
||||
temp_wav = temp_dir / "voiceover.wav"
|
||||
create_voiceover_from_srt(srt_path, temp_wav, voice, rate, temp_dir)
|
||||
combine_video_audio(video_file, temp_wav, output_file, args.reencode_video)
|
||||
create_voiceover_from_srt(
|
||||
srt_path,
|
||||
temp_wav,
|
||||
voice,
|
||||
rate,
|
||||
temp_dir,
|
||||
args.volume,
|
||||
args.silence_padding,
|
||||
args.sample_rate,
|
||||
args.channels,
|
||||
)
|
||||
if audio_output:
|
||||
run(["ffmpeg", "-y", "-i", str(temp_wav), "-c:a", "pcm_s16le", str(audio_output)])
|
||||
print(f"Done: {audio_output}")
|
||||
combine_video_audio(video_file, temp_wav, output_file, args.reencode_video, 1.0)
|
||||
finally:
|
||||
remove_temp_dir(temp_dir)
|
||||
if not args.keep_temp:
|
||||
remove_temp_dir(temp_dir)
|
||||
else:
|
||||
print(f"Kept temporary voice parts: {temp_dir}")
|
||||
|
||||
print(f"Done: {output_file}")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -33,7 +34,13 @@ def resolve_path(value: str) -> Path:
|
||||
|
||||
def media_duration(path: Path) -> float:
|
||||
result = subprocess.run(
|
||||
["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(path)],
|
||||
[
|
||||
"ffprobe",
|
||||
"-v", "error",
|
||||
"-show_entries", "format=duration",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
str(path),
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
@@ -46,6 +53,15 @@ def run(command: list[str]) -> None:
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
|
||||
def remove_temp_dir(path: Path) -> None:
|
||||
try:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
except PermissionError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Create a timed voiceover WAV/MP3 from an SRT file on macOS.")
|
||||
parser.add_argument("--srt", required=True, help="Input SRT file")
|
||||
@@ -53,54 +69,80 @@ def main() -> None:
|
||||
parser.add_argument("--rate", type=int, default=185, help="Speech rate. Higher is faster. Example: 180-200")
|
||||
parser.add_argument("--wav", required=True, help="Output WAV file")
|
||||
parser.add_argument("--mp3", default=None, help="Optional output MP3 file")
|
||||
parser.add_argument("--audio-output", default=None, help="Optional extra copy of the generated WAV file")
|
||||
parser.add_argument("--keep-temp", action="store_true", help="Keep temporary voice parts for debugging")
|
||||
parser.add_argument("--volume", type=float, default=1.0, help="Voiceover volume multiplier. Default: 1.0")
|
||||
parser.add_argument("--silence-padding", type=float, default=0.0, help="Seconds of silence after each caption, if the SRT slot allows it. Default: 0.0")
|
||||
parser.add_argument("--sample-rate", type=int, default=44100, help="Output sample rate. Default: 44100")
|
||||
parser.add_argument("--channels", type=int, default=2, choices=[1, 2], help="Output audio channels. Default: 2")
|
||||
args = parser.parse_args()
|
||||
|
||||
srt_path = resolve_path(args.srt)
|
||||
wav_output = resolve_path(args.wav)
|
||||
mp3_output = resolve_path(args.mp3) if args.mp3 else None
|
||||
audio_output = resolve_path(args.audio_output) if args.audio_output else None
|
||||
work_dir = Path(__file__).resolve().parent / "voice_parts"
|
||||
|
||||
if not srt_path.exists():
|
||||
raise FileNotFoundError(f"SRT not found: {srt_path}")
|
||||
if args.volume <= 0:
|
||||
raise ValueError("--volume must be greater than 0")
|
||||
if args.silence_padding < 0:
|
||||
raise ValueError("--silence-padding must be 0 or greater")
|
||||
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
wav_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if mp3_output:
|
||||
mp3_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if audio_output:
|
||||
audio_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
captions = read_srt(srt_path)
|
||||
segment_files = []
|
||||
|
||||
for i, (start, end, text) in enumerate(captions, 1):
|
||||
duration = end - start
|
||||
raw_aiff = work_dir / f"raw_{i:03d}.aiff"
|
||||
segment_wav = work_dir / f"seg_{i:03d}.wav"
|
||||
try:
|
||||
for i, (start, end, text) in enumerate(captions, 1):
|
||||
duration = end - start
|
||||
raw_aiff = work_dir / f"raw_{i:03d}.aiff"
|
||||
segment_wav = work_dir / f"seg_{i:03d}.wav"
|
||||
|
||||
run(["say", "-v", args.voice, "-r", str(args.rate), "-o", str(raw_aiff), text])
|
||||
run(["say", "-v", args.voice, "-r", str(args.rate), "-o", str(raw_aiff), text])
|
||||
|
||||
spoken_duration = media_duration(raw_aiff)
|
||||
if spoken_duration > duration:
|
||||
print(f"Warning: caption {i} speech is {spoken_duration:.2f}s but slot is {duration:.2f}s; it will be trimmed.")
|
||||
spoken_duration = media_duration(raw_aiff)
|
||||
padding = min(args.silence_padding, max(duration - spoken_duration, 0))
|
||||
effective_duration = duration if args.silence_padding == 0 else min(duration, spoken_duration + padding)
|
||||
|
||||
run([
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(raw_aiff),
|
||||
"-af", f"apad,atrim=0:{duration}",
|
||||
"-ar", "44100",
|
||||
"-ac", "2",
|
||||
str(segment_wav),
|
||||
])
|
||||
segment_files.append(segment_wav)
|
||||
if spoken_duration > duration:
|
||||
print(f"Warning: caption {i} speech is {spoken_duration:.2f}s but slot is {duration:.2f}s; it will be trimmed.")
|
||||
|
||||
concat_list = work_dir / "list.txt"
|
||||
concat_list.write_text("".join(f"file '{os.path.abspath(file)}'\n" for file in segment_files), encoding="utf-8")
|
||||
audio_filter = f"volume={args.volume},apad,atrim=0:{effective_duration}"
|
||||
|
||||
run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_list), "-c:a", "pcm_s16le", str(wav_output)])
|
||||
print(f"Done: {wav_output}")
|
||||
run([
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(raw_aiff),
|
||||
"-af", audio_filter,
|
||||
"-ar", str(args.sample_rate),
|
||||
"-ac", str(args.channels),
|
||||
str(segment_wav),
|
||||
])
|
||||
segment_files.append(segment_wav)
|
||||
|
||||
if mp3_output:
|
||||
run(["ffmpeg", "-y", "-i", str(wav_output), "-codec:a", "libmp3lame", "-b:a", "192k", str(mp3_output)])
|
||||
print(f"Done: {mp3_output}")
|
||||
concat_list = work_dir / "list.txt"
|
||||
concat_list.write_text("".join(f"file '{os.path.abspath(file)}'\n" for file in segment_files), encoding="utf-8")
|
||||
|
||||
run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_list), "-c:a", "pcm_s16le", str(wav_output)])
|
||||
print(f"Done: {wav_output}")
|
||||
|
||||
if audio_output:
|
||||
run(["ffmpeg", "-y", "-i", str(wav_output), "-c:a", "pcm_s16le", str(audio_output)])
|
||||
print(f"Done: {audio_output}")
|
||||
|
||||
if mp3_output:
|
||||
run(["ffmpeg", "-y", "-i", str(wav_output), "-codec:a", "libmp3lame", "-b:a", "192k", str(mp3_output)])
|
||||
print(f"Done: {mp3_output}")
|
||||
finally:
|
||||
if not args.keep_temp:
|
||||
remove_temp_dir(work_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -56,12 +57,22 @@ def run(command: list[str]) -> None:
|
||||
subprocess.run(command, check=True)
|
||||
|
||||
|
||||
def speak_to_wav(text: str, voice: str, rate: int, output: Path) -> None:
|
||||
def remove_temp_dir(path: Path) -> None:
|
||||
try:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
except PermissionError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def speak_to_wav(text: str, voice: str, rate: int, volume: int, output: Path) -> None:
|
||||
ps_script = f"""
|
||||
Add-Type -AssemblyName System.Speech
|
||||
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
|
||||
$synth.SelectVoice({ps_quote(voice)})
|
||||
$synth.Rate = {rate}
|
||||
$synth.Volume = {volume}
|
||||
$synth.SetOutputToWaveFile({ps_quote(str(output))})
|
||||
$synth.Speak({ps_quote(text)})
|
||||
$synth.Dispose()
|
||||
@@ -76,54 +87,84 @@ def main() -> None:
|
||||
parser.add_argument("--rate", type=int, default=0, help="Windows speech rate from -10 to 10. Higher is faster.")
|
||||
parser.add_argument("--wav", required=True, help="Output WAV file")
|
||||
parser.add_argument("--mp3", default=None, help="Optional output MP3 file")
|
||||
parser.add_argument("--audio-output", default=None, help="Optional extra copy of the generated WAV file")
|
||||
parser.add_argument("--keep-temp", action="store_true", help="Keep temporary voice parts for debugging")
|
||||
parser.add_argument("--volume", type=float, default=1.0, help="Voiceover volume multiplier. Default: 1.0")
|
||||
parser.add_argument("--silence-padding", type=float, default=0.0, help="Seconds of silence after each caption, if the SRT slot allows it. Default: 0.0")
|
||||
parser.add_argument("--sample-rate", type=int, default=44100, help="Output sample rate. Default: 44100")
|
||||
parser.add_argument("--channels", type=int, default=2, choices=[1, 2], help="Output audio channels. Default: 2")
|
||||
args = parser.parse_args()
|
||||
|
||||
srt_path = resolve_path(args.srt)
|
||||
wav_output = resolve_path(args.wav)
|
||||
mp3_output = resolve_path(args.mp3) if args.mp3 else None
|
||||
audio_output = resolve_path(args.audio_output) if args.audio_output else None
|
||||
work_dir = Path(__file__).resolve().parent / "voice_parts"
|
||||
|
||||
if not srt_path.exists():
|
||||
raise FileNotFoundError(f"SRT not found: {srt_path}")
|
||||
if args.volume <= 0:
|
||||
raise ValueError("--volume must be greater than 0")
|
||||
if args.silence_padding < 0:
|
||||
raise ValueError("--silence-padding must be 0 or greater")
|
||||
|
||||
windows_volume = max(0, min(100, round(args.volume * 100)))
|
||||
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
wav_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if mp3_output:
|
||||
mp3_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if audio_output:
|
||||
audio_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
captions = read_srt(srt_path)
|
||||
segment_files = []
|
||||
|
||||
for i, (start, end, text) in enumerate(captions, 1):
|
||||
duration = end - start
|
||||
raw_wav = work_dir / f"raw_{i:03d}.wav"
|
||||
segment_wav = work_dir / f"seg_{i:03d}.wav"
|
||||
try:
|
||||
for i, (start, end, text) in enumerate(captions, 1):
|
||||
duration = end - start
|
||||
raw_wav = work_dir / f"raw_{i:03d}.wav"
|
||||
segment_wav = work_dir / f"seg_{i:03d}.wav"
|
||||
|
||||
speak_to_wav(text, args.voice, args.rate, raw_wav)
|
||||
speak_to_wav(text, args.voice, args.rate, windows_volume, raw_wav)
|
||||
|
||||
spoken_duration = media_duration(raw_wav)
|
||||
if spoken_duration > duration:
|
||||
print(f"Warning: caption {i} speech is {spoken_duration:.2f}s but slot is {duration:.2f}s; it will be trimmed.")
|
||||
spoken_duration = media_duration(raw_wav)
|
||||
padding = min(args.silence_padding, max(duration - spoken_duration, 0))
|
||||
effective_duration = duration if args.silence_padding == 0 else min(duration, spoken_duration + padding)
|
||||
|
||||
run([
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(raw_wav),
|
||||
"-af", f"apad,atrim=0:{duration}",
|
||||
"-ar", "44100",
|
||||
"-ac", "2",
|
||||
str(segment_wav),
|
||||
])
|
||||
segment_files.append(segment_wav)
|
||||
if spoken_duration > duration:
|
||||
print(f"Warning: caption {i} speech is {spoken_duration:.2f}s but slot is {duration:.2f}s; it will be trimmed.")
|
||||
|
||||
concat_list = work_dir / "list.txt"
|
||||
concat_list.write_text("".join(f"file '{os.path.abspath(file)}'\n" for file in segment_files), encoding="utf-8")
|
||||
# Windows System.Speech volume is limited to 0-100. Use ffmpeg volume only for multipliers above 1.0.
|
||||
ffmpeg_volume = max(args.volume, 1.0)
|
||||
audio_filter = f"volume={ffmpeg_volume},apad,atrim=0:{effective_duration}"
|
||||
|
||||
run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_list), "-c:a", "pcm_s16le", str(wav_output)])
|
||||
print(f"Done: {wav_output}")
|
||||
run([
|
||||
"ffmpeg", "-y",
|
||||
"-i", str(raw_wav),
|
||||
"-af", audio_filter,
|
||||
"-ar", str(args.sample_rate),
|
||||
"-ac", str(args.channels),
|
||||
str(segment_wav),
|
||||
])
|
||||
segment_files.append(segment_wav)
|
||||
|
||||
if mp3_output:
|
||||
run(["ffmpeg", "-y", "-i", str(wav_output), "-codec:a", "libmp3lame", "-b:a", "192k", str(mp3_output)])
|
||||
print(f"Done: {mp3_output}")
|
||||
concat_list = work_dir / "list.txt"
|
||||
concat_list.write_text("".join(f"file '{os.path.abspath(file)}'\n" for file in segment_files), encoding="utf-8")
|
||||
|
||||
run(["ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", str(concat_list), "-c:a", "pcm_s16le", str(wav_output)])
|
||||
print(f"Done: {wav_output}")
|
||||
|
||||
if audio_output:
|
||||
run(["ffmpeg", "-y", "-i", str(wav_output), "-c:a", "pcm_s16le", str(audio_output)])
|
||||
print(f"Done: {audio_output}")
|
||||
|
||||
if mp3_output:
|
||||
run(["ffmpeg", "-y", "-i", str(wav_output), "-codec:a", "libmp3lame", "-b:a", "192k", str(mp3_output)])
|
||||
print(f"Done: {mp3_output}")
|
||||
finally:
|
||||
if not args.keep_temp:
|
||||
remove_temp_dir(work_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user