import numpy as np import matplotlib.pyplot as plt from pydub import AudioSegment from scipy.fftpack import fft import os import tempfile from sklearn.linear_model import LinearRegression from pydub.exceptions import CouldntDecodeError import subprocess from lab_tools import ytutil # YouTubeから音声をダウンロードし、指定範囲の音声データを返す def download_and_process_audio(youtube_url: str, start_time: float, end_time: float = None): filename = ytutil.download_youtube(youtube_url) audio = AudioSegment.from_mp3(filename) os.remove(filename) # 時間範囲をミリ秒単位に変換 if end_time is None: end_time = len(audio) / 1000.0 # 音声の長さを秒単位で取得 start_ms = start_time * 1000 end_ms = end_time * 1000 audio = audio[start_ms:end_ms] return audio, filename def convert_to_mp3(input_file: str, output_file: str): try: subprocess.run( ["ffmpeg", "-i", input_file, "-q:a", "0", "-map", "a", output_file], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) except subprocess.CalledProcessError as e: raise RuntimeError(f"ffmpegによるMP3変換に失敗しました: {e.stderr.decode()}") def process_audio(filename: str, start_time: float, end_time: float = None): mp3_filename = filename.rsplit('.', 1)[0] + ".mp3" if not filename.endswith(".mp3"): convert_to_mp3(filename, mp3_filename) filename = mp3_filename try: audio = AudioSegment.from_mp3(filename) except CouldntDecodeError: raise ValueError(f"MP3ファイル {filename} のデコードに失敗しました。ファイルが破損している可能性があります。") os.remove(filename) # 時間範囲をミリ秒単位に変換 if end_time is None: end_time = len(audio) / 1000.0 # 音声の長さを秒単位で取得 start_ms = start_time * 1000 end_ms = end_time * 1000 audio = audio[start_ms:end_ms] return audio, mp3_filename # 変換後のファイル名を返す # 音声データをモノラルに変換して、NumPy配列とサンプリングレートを返す def convert_audio_to_numpy(audio: AudioSegment): data = np.array(audio.get_array_of_samples()) sample_rate = audio.frame_rate # ステレオ音声の場合はモノラルに変換 if audio.channels > 1: data = data.reshape((-1, audio.channels)).mean(axis=1) return data, sample_rate # パワースペクトルを計算 def compute_power_spectrum(data: np.ndarray, sample_rate: int): N = len(data) T = 1.0 / sample_rate yf = fft(data) xf = np.fft.fftfreq(N, T)[:N // 2] power_spectrum = 2.0 / N * np.abs(yf[:N // 2]) # 対数スケールに変換 xf_log = np.log10(xf[1:]) power_spectrum_log = np.log10(power_spectrum[1:]) return xf_log, power_spectrum_log # 線形回帰によるパワースペクトルの近似 def perform_linear_regression(xf_log: np.ndarray, power_spectrum_log: np.ndarray): xf_log_reshaped = xf_log.reshape(-1, 1) reg = LinearRegression().fit(xf_log_reshaped, power_spectrum_log) power_spectrum_pred = reg.predict(xf_log_reshaped) return power_spectrum_pred, reg # パワースペクトルと線形近似のグラフを保存 def plot_and_save_spectrum(xf_log: np.ndarray, power_spectrum_log: np.ndarray, power_spectrum_pred: np.ndarray, sample_rate: int): graphfile_path = tempfile.NamedTemporaryFile(delete=False, suffix='.png').name # グラフの描画 plt.figure(figsize=(10, 6)) plt.plot(10**xf_log, 10**power_spectrum_log, label='Power Spectrum') plt.plot(10**xf_log, 10**power_spectrum_pred, color='red', linestyle='--', label='Linear Approximation') plt.xscale('log') plt.yscale('log') plt.title('Power Spectrum (Log Scale)') plt.xlabel('Frequency (Hz)') plt.ylabel('Power') plt.grid(True, which="both", ls="--") plt.xlim([1, sample_rate // 2]) plt.legend() plt.savefig(graphfile_path) return graphfile_path # メインの解析関数 Use YouTube def analyze_1f_noise(mode: str, youtube_url: str, file_path: str, start_time: float = 0, end_time: float = None): # 音声データの取得 if mode == "YouTube": audio, filename = download_and_process_audio(youtube_url, start_time, end_time) else: audio, filename = process_audio(file_path, start_time, end_time) # 音声データをNumPy配列に変換 data, sample_rate = convert_audio_to_numpy(audio) # パワースペクトルを計算 xf_log, power_spectrum_log = compute_power_spectrum(data, sample_rate) # 線形回帰を実行 power_spectrum_pred, _ = perform_linear_regression(xf_log, power_spectrum_log) # グラフを描画して保存 graphfile_path = plot_and_save_spectrum(xf_log, power_spectrum_log, power_spectrum_pred, sample_rate) # ファイル名を返す filename, _ = os.path.splitext(filename) return filename, graphfile_path