指定された要件に合わせて mix 関数を修正し、ポッドキャストの最初と最後に5秒間のBGMを挿入し、ポッドキャスト再生中はBGMの音量を10%(-20dB)に調整するようにします。
unfold_lessapp.py-2+39
content_copyfullscreencheck
warning_amberThe code change produced by Gemini cannot be automatically applied. You can manually apply the change or ask Gemini to try again.File a report
bgm_path = os.path.join(BGM_FOLDER, bgm_name)
bgm_audio = AudioSegment.from_file(bgm_path)
mixed = podcast.overlay(bgm_audio, loop=True)
intro_duration_ms = 5000
outro_duration_ms = 5000
podcast_duration_ms = len(podcast)
# BGMをポッドキャスト再生中に10%の音量にする(約-20dB)
# 20 * log10(0.1) = -20 dB
bgm_duck_db = -20.0
# 最終的なミックスファイルの総再生時間
total_mixed_duration_ms = intro_duration_ms + podcast_duration_ms + outro_duration_ms
# 1. BGM全体を作成(ループとトリミング)
# 必要な長さに応じてBGMをループさせ、総再生時間に合わせる
full_bgm = bgm_audio
if len(full_bgm) < total_mixed_duration_ms:
# math.ceilの整数版: (分子 + 分母 - 1) // 分母
num_loops = (total_mixed_duration_ms + len(full_bgm) - 1) // len(full_bgm)
full_bgm = full_bgm * num_loops
full_bgm = full_bgm[:total_mixed_duration_ms]
# 2. BGMの各パートを抽出
intro_bgm_part = full_bgm[:intro_duration_ms]
body_bgm_start_ms = intro_duration_ms
body_bgm_end_ms = intro_duration_ms + podcast_duration_ms
body_bgm_part = full_bgm[body_bgm_start_ms:body_bgm_end_ms]
outro_bgm_start_ms = body_bgm_end_ms
outro_bgm_part = full_bgm[outro_bgm_start_ms:total_mixed_duration_ms]
# 3. ポッドキャスト部分を作成(BGMをダッキングしてポッドキャストをオーバーレイ)
# body_bgm_partの音量を下げつつ、podcastを重ねる
# podcastとbody_bgm_partの長さは同じはず
body_with_podcast_ducked_bgm = body_bgm_part.overlay(podcast, gain_during_overlay=bgm_duck_db)
# 4. 全てのパートを結合
final_mix = intro_bgm_part + body_with_podcast_ducked_bgm + outro_bgm_part
output_name = f"{uuid.uuid4().hex}.mp3"
output_path = os.path.join(OUTPUT_FOLDER, output_name)
mixed.export(output_path, format='mp3')
final_mix.export(output_path, format='mp3')
return send_file(output_path, as_attachment=True, download_name='mixed.mp3')
thumb_upthumb_downHelp us improve
Context Sources (14)
c:\Users\takam\Documents\GitHub\PPSG\app.pyc:\Users\takam\.vscode\extensions\ms-python.vscode-pylance-2025.6.1\dist\typeshed-fallback\stdlib\os\init.pyic:\Users\takam\Documents\GitHub\PPSG\.venv\Lib\site-packages\pydub\init.pyc:\Users\takam\Documents\GitHub\PPSG\.venv\Lib\site-packages\pydub\audio_segment.pyc:\Users\takam\Documents\GitHub\PPSG\.venv\Lib\site-packages\pydub\effects.pyshow 9 more
はい、承知いたしました。出力される音声の最後の1秒間でBGMがフェードアウトするように修正します。
具体的には、outro_bgm_part を作成した後、その音声セグメントに対して fade_out() メソッドを適用します。
unfold_lessapp.py-0+7
content_copyfullscreencheck
warning_amberThe code change produced by Gemini cannot be automatically applied. You can manually apply the change or ask Gemini to try again.File a report
outro_bgm_start_ms = body_bgm_end_ms
outro_bgm_part = full_bgm[outro_bgm_start_ms:total_mixed_duration_ms]
# アウトロBGMの最後の1秒をフェードアウト
fade_out_duration_ms = 1000
if len(outro_bgm_part) >= fade_out_duration_ms:
outro_bgm_part = outro_bgm_part.fade_out(fade_out_duration_ms)
elif len(outro_bgm_part) > 0: # フェードアウト期間より短い場合は、全期間でフェードアウト
outro_bgm_part = outro_bgm_part.fade_out(len(outro_bgm_part))
# 3. ポッドキャスト部分を作成(BGMをダッキングしてポッドキャストをオーバーレイ)
# body_bgm_partの音量を下げつつ、podcastを重ねる
# podcastとbody_bgm_partの長さは同じはず
thumb_upthumb_downHelp us improve
Context Sources (1)
c:\Users\takam\Documents\GitHub\PPSG\app.py
はい、承知いたしました。mix 関数の各部分がどのような処理を行っているか解説します。
unfold_lesspython
content_copyadd
import os
import uuid
from flask import Flask, render_template, request, redirect, url_for, send_file
from mutagen.easyid3 import EasyID3
from pydub import AudioSegment
app = Flask(__name__)
BGM_FOLDER = os.path.join(os.path.dirname(__file__), 'bgm')
OUTPUT_FOLDER = os.path.join(os.path.dirname(__file__), 'output')
os.makedirs(BGM_FOLDER, exist_ok=True)
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
def get_bgm_options():
options = []
for fname in os.listdir(BGM_FOLDER):
if not fname.lower().endswith('.mp3'):
continue
path = os.path.join(BGM_FOLDER, fname)
try:
tags = EasyID3(path)
title = tags.get('title', [fname])[0]
comment = tags.get('comment', [''])[0]
label = f"{title} - {comment}" if comment else title
except Exception:
label = fname
options.append({'file': fname, 'label': label})
return options
@app.route('/')
def index():
options = get_bgm_options()
return render_template('index.html', options=options)
@app.route('/mix', methods=['POST'])
def mix():
# 1. リクエストから音声ファイルとBGMファイル名を取得
file = request.files.get('audio') # アップロードされたポッドキャスト音声ファイル
bgm_name = request.form.get('bgm') # 選択されたBGMのファイル名
if not file or not bgm_name: # ファイルかBGM名がない場合はトップページへリダイレクト
return redirect(url_for('index'))
# 2. 音声ファイルをAudioSegmentオブジェクトとして読み込み
podcast = AudioSegment.from_file(file) # ポッドキャスト音声
bgm_path = os.path.join(BGM_FOLDER, bgm_name) # BGMファイルのフルパス
bgm_audio = AudioSegment.from_file(bgm_path) # BGM音声
# この行は後続の処理で上書きされるため、実質的には使用されていません。
# mixed = podcast.overlay(bgm_audio, loop=True)
# 3. 各種時間設定 (ミリ秒単位)
intro_duration_ms = 5000 # イントロBGMの長さ (5秒)
outro_duration_ms = 5000 # アウトロBGMの長さ (5秒)
podcast_duration_ms = len(podcast) # ポッドキャスト本体の長さ
# 4. BGMの音量調整設定
# ポッドキャスト再生中、BGMの音量を10%に下げる (-20dBに相当)
bgm_duck_db = -20.0
# 5. 最終的なミックスファイルの総再生時間を計算
total_mixed_duration_ms = intro_duration_ms + podcast_duration_ms + outro_duration_ms
# 6. BGM全体の準備
# 6-1. BGM音声のコピーを作成
full_bgm = bgm_audio
# 6-2. BGMの長さが総再生時間より短い場合、ループさせて総再生時間以上にする
if len(full_bgm) < total_mixed_duration_ms:
# 必要なループ回数を計算 (切り上げ)
num_loops = (total_mixed_duration_ms + len(full_bgm) - 1) // len(full_bgm)
full_bgm = full_bgm * num_loops # BGMをループ
# 6-3. BGMを総再生時間の長さにトリミング
full_bgm = full_bgm[:total_mixed_duration_ms]
# 7. BGMを各パートに分割
# 7-1. イントロ部分のBGMを抽出
intro_bgm_part = full_bgm[:intro_duration_ms]
# 7-2. ポッドキャスト再生中のBGM部分の開始・終了時間を計算
body_bgm_start_ms = intro_duration_ms
body_bgm_end_ms = intro_duration_ms + podcast_duration_ms
# 7-3. ポッドキャスト再生中のBGM部分を抽出
body_bgm_part = full_bgm[body_bgm_start_ms:body_bgm_end_ms]
# 7-4. アウトロ部分のBGMの開始時間を計算
outro_bgm_start_ms = body_bgm_end_ms
# 7-5. アウトロ部分のBGMを抽出
outro_bgm_part = full_bgm[outro_bgm_start_ms:total_mixed_duration_ms]
# 8. アウトロBGMのフェードアウト処理
fade_out_duration_ms = 1000 # フェードアウト時間 (1秒)
if len(outro_bgm_part) >= fade_out_duration_ms:
# アウトロBGMの長さがフェードアウト時間以上なら、指定時間でフェードアウト
outro_bgm_part = outro_bgm_part.fade_out(fade_out_duration_ms)
elif len(outro_bgm_part) > 0:
# アウトロBGMの長さがフェードアウト時間より短い場合は、アウトロBGM全体の長さでフェードアウト
outro_bgm_part = outro_bgm_part.fade_out(len(outro_bgm_part))
# 9. ポッドキャスト本体部分の作成
# ポッドキャスト再生中のBGM (body_bgm_part) の音量を下げつつ (gain_during_overlay=bgm_duck_db)、
# ポッドキャスト音声 (podcast) を重ねる (オーバーレイ)
body_with_podcast_ducked_bgm = body_bgm_part.overlay(podcast, gain_during_overlay=bgm_duck_db)
# 10. 全てのパートを結合して最終的な音声を作成
# イントロBGM + (ポッドキャスト音声 + 音量を下げたBGM) + アウトロBGM(フェードアウト付き)
final_mix = intro_bgm_part + body_with_podcast_ducked_bgm + outro_bgm_part
# 11. 出力ファイル名とパスを生成 (UUIDを使用して一意なファイル名にする)
# この output_name と output_path は、この直前で定義された同名の変数を上書きします。
# 最初の output_name, output_path の定義と mixed.export は実質的に不要です。
output_name = f"{uuid.uuid4().hex}.mp3"
output_path = os.path.join(OUTPUT_FOLDER, output_name)
# mixed.export(output_path, format='mp3') # この行は不要
# 12. 最終的なミックス音声をMP3ファイルとしてエクスポート
final_mix.export(output_path, format='mp3')
# 13. 生成されたMP3ファイルをクライアントに送信 (ダウンロードさせる)
return send_file(output_path, as_attachment=True, download_name='mixed.mp3')
if __name__ == '__main__':
app.run(debug=True)
処理の流れの概要: