File size: 5,066 Bytes
145a95e
 
 
 
 
 
2327f3b
 
 
145a95e
2327f3b
145a95e
 
2327f3b
 
 
 
 
145a95e
2327f3b
 
 
 
 
 
145a95e
2327f3b
145a95e
2327f3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bb9215b
145a95e
2327f3b
 
 
 
 
 
 
 
 
 
 
 
 
145a95e
 
 
2327f3b
145a95e
 
 
2327f3b
 
 
 
 
145a95e
 
 
2327f3b
 
 
 
 
 
 
 
 
145a95e
2327f3b
 
 
 
 
145a95e
2327f3b
145a95e
2327f3b
 
 
145a95e
 
 
 
2327f3b
 
145a95e
 
 
 
 
 
 
 
2327f3b
145a95e
 
2327f3b
 
145a95e
2327f3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145a95e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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