|
import argparse |
|
import subprocess |
|
import os |
|
import tempfile |
|
|
|
def run(cmd): |
|
print(f"Running: {' '.join(cmd)}") |
|
subprocess.run(cmd, check=True) |
|
|
|
def get_audio_duration(path): |
|
result = subprocess.run( |
|
['ffprobe', '-v', 'error', '-show_entries', 'format=duration', |
|
'-of', 'default=noprint_wrappers=1:nokey=1', path], |
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True |
|
) |
|
return float(result.stdout.strip()) |
|
|
|
def main(): |
|
parser = argparse.ArgumentParser(description='VOICEVOX音声とBGMを合成して通知音を作成') |
|
parser.add_argument('--voice', required=True, help='VOICEVOX 音声ファイル (wav)') |
|
parser.add_argument('--bgm', required=True, help='BGM 音声ファイル (mp3/wav/etc)') |
|
parser.add_argument('--duration', type=float, required=True, help='出力音声の長さ(秒)') |
|
parser.add_argument('--output', default='output.wav', help='出力ファイル名 (wav)') |
|
args = parser.parse_args() |
|
|
|
with tempfile.TemporaryDirectory() as tmpdir: |
|
bgm_wav = os.path.join(tmpdir, 'bgm.wav') |
|
bgm_fade = os.path.join(tmpdir, 'bgm_fade.wav') |
|
|
|
# BGM を wav 形式に変換 |
|
run(['ffmpeg', '-y', '-i', args.bgm, '-ar', '24000', '-ac', '1', bgm_wav]) |
|
|
|
# BGM の実際の長さを取得 |
|
bgm_duration = get_audio_duration(bgm_wav) |
|
fade_start = max(0, args.duration - 5) |
|
|
|
# フェードアウト & 音量調整(BGM: -5dB) |
|
run([ |
|
'ffmpeg', '-y', '-i', bgm_wav, |
|
'-af', f'volume=0.56,afade=t=out:st={fade_start}:d=5', |
|
bgm_fade |
|
]) |
|
|
|
# 合成(VOICE: +5dB) & トリミング |
|
run([ |
|
'ffmpeg', '-y', |
|
'-i', bgm_fade, |
|
'-i', args.voice, |
|
'-filter_complex', '[1]volume=1.78,adelay=0|0[voice];[0][voice]amix=inputs=2:duration=first:dropout_transition=2', |
|
'-t', str(args.duration), |
|
args.output |
|
]) |
|
|
|
print(f"\n✅ 成功: {args.output}(長さ: 約 {args.duration} 秒)") |
|
|
|
if __name__ == '__main__': |
|
main() |