naohiro701 commited on
Commit
e531916
·
verified ·
1 Parent(s): 6a7c8d1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +111 -187
app.py CHANGED
@@ -1,198 +1,122 @@
 
1
  import numpy as np
2
  import matplotlib.pyplot as plt
3
- from matplotlib.animation import FuncAnimation, FFMpegWriter
4
- from pydub import AudioSegment
5
- from scipy.fft import rfft, rfftfreq
6
- import streamlit as st
7
  import tempfile
8
  import os
9
  import subprocess
10
- import colorsys
11
-
12
- # ----- 設定 -----
13
- BASE_FREQUENCY = 440 # ラ(A4)基準
14
- NOTE_NAMES = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"]
15
-
16
- def frequency_to_color(freq):
17
- """周波数 freq をノートに変換し、そのノートに対応したカラーを HSV->RGB で返す簡易関数。"""
18
- if freq < BASE_FREQUENCY / 2:
19
- return (0.5, 0.5, 0.5) # gray
20
 
21
- semitone_index = int(round(12 * np.log2(freq / BASE_FREQUENCY)))
22
- note_idx = semitone_index % 12
23
-
24
- # HSV 空間で色相を note_idx/12 として割り当て (S=1.0, V=1.0)
25
- color_hsv = (note_idx / 12, 1.0, 1.0)
26
- return colorsys.hsv_to_rgb(*color_hsv)
27
 
28
  def main():
29
- st.title("周波数特性アート動画生成 (改良版)")
30
-
31
- uploaded_file = st.file_uploader("音声ファイルをアップロード (MP3)", type=["mp3"])
32
- if uploaded_file is None:
33
- st.info("MP3ファイルをアップロードしてください。")
34
- return
35
-
36
- # ----- Step1: MP3 -> AudioSegment 変換 -----
37
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_mp3:
38
- temp_mp3.write(uploaded_file.read())
39
- audio = AudioSegment.from_file(temp_mp3.name)
40
-
41
- # ----- Step2: numpy配列化 -----
42
- samples = np.array(audio.get_array_of_samples(), dtype=float)
43
- sample_rate = audio.frame_rate
44
-
45
- # ステレオモノラル
46
- if audio.channels == 2:
47
- samples = samples.reshape((-1, 2)).mean(axis=1)
48
-
49
- # 正規化(-1~1)
50
- samples /= np.iinfo(audio.array_type).max
51
-
52
- st.write(f"サンプリングレート: {sample_rate} Hz")
53
- st.write(f"サンプル数: {len(samples)}")
54
-
55
- # FFT パラメータ
56
- chunk_size = 2048
57
- overlap = 1024
58
- step_size = chunk_size - overlap
59
- freqs = rfftfreq(chunk_size, d=1/sample_rate)
60
-
61
- # チャンク数
62
- n_chunks = (len(samples) - chunk_size) // step_size + 1
63
- st.write(f"フレーム数: {n_chunks}")
64
-
65
- # 各チャンクの FFT -> ピーク周波数 & 総エネルギー取得
66
- peak_freqs = []
67
- total_amps = []
68
-
69
- window = np.hanning(chunk_size)
70
-
71
- for i in range(n_chunks):
72
- start = i * step_size
73
- end = start + chunk_size
74
- chunk = samples[start:end] * window
75
- spectrum = np.abs(rfft(chunk))
76
-
77
- peak_index = np.argmax(spectrum)
78
- peak_freq = freqs[peak_index]
79
- peak_freqs.append(peak_freq)
80
-
81
- total_amp = np.sum(spectrum)
82
- total_amps.append(total_amp)
83
-
84
- peak_freqs = np.array(peak_freqs)
85
- total_amps = np.array(total_amps)
86
-
87
- # ----- Step3: Matplotlib アニメーション作成 -----
88
- fig, ax = plt.subplots(figsize=(6, 6))
89
- # まずは白背景でテストする場合
90
- fig.patch.set_facecolor("white")
91
- ax.set_facecolor("white") # 背景を白に変更
92
- ax.set_xlim(-2, 2) # 描画範囲を少し拡大
93
- ax.set_ylim(-2, 2)
94
- ax.set_aspect("equal")
95
- ax.axis("off")
96
-
97
- # 初期化: 座標は空
98
- scatter_plot = ax.scatter(
99
- np.empty(0),
100
- np.empty(0),
101
- s=10,
102
- c=[],
103
- alpha=0.9
104
- )
105
-
106
- # スパイラルのベース座標
107
- num_points = 200
108
- angles = np.linspace(0, 4 * np.pi, num_points)
109
- radii = np.linspace(0.05, 0.5, num_points)
110
- x_base = radii * np.cos(angles)
111
- y_base = radii * np.sin(angles)
112
-
113
- def init():
114
- # 2次元配列で空データを渡す
115
- scatter_plot.set_offsets(np.empty((0, 2)))
116
- return (scatter_plot,)
117
-
118
- def update(frame):
119
- """
120
- 各フレームで呼ばれる描画更新関数。
121
- frame: 0 ~ n_chunks-1
122
- """
123
- p_freq = peak_freqs[frame] # ピーク周波数
124
- c = frequency_to_color(p_freq) # 周波数に応じた色
125
- amp_scale = np.log10(total_amps[frame] + 1) # 総エネルギー -> 対数スケール
126
-
127
- # スパイラル座標フレームごとに回転・変形
128
- theta_shift = 0.1 * frame
129
- x_mod = x_base * (1 + 0.05 * np.sin(theta_shift))
130
- y_mod = y_base * (1 + 0.05 * np.cos(theta_shift))
131
-
132
- rot = 0.05 * frame
133
- cos_r = np.cos(rot)
134
- sin_r = np.sin(rot)
135
- x_rot = x_mod * cos_r - y_mod * sin_r
136
- y_rot = x_mod * sin_r + y_mod * cos_r
137
-
138
- # 座標更新
139
- coords = np.column_stack((x_rot, y_rot))
140
- scatter_plot.set_offsets(coords)
141
-
142
- # 点のサイズ更新 (もっと大きくしてみる)
143
- sizes = (100 + 300 * amp_scale) * np.ones(num_points)
144
- scatter_plot.set_sizes(sizes)
145
-
146
- # 全点同じ色
147
- colors = np.array([c for _ in range(num_points)])
148
- scatter_plot.set_facecolor(colors)
149
-
150
- return (scatter_plot,)
151
-
152
- # blit=False でアニメーションを作る
153
- ani = FuncAnimation(
154
- fig,
155
- update,
156
- frames=n_chunks,
157
- init_func=init,
158
- interval=50, # インターバル少し長めに
159
- blit=False # ← 重要ポイント
160
- )
161
-
162
- # ----- Step4: Matplotlib アニメーションを一時ファイルに保存 (.mp4) -----
163
- video_temp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
164
- video_path = video_temp.name
165
- video_temp.close()
166
-
167
- writer = FFMpegWriter(fps=30, codec="libx264")
168
- ani.save(video_path, writer=writer, dpi=150)
169
- plt.close(fig)
170
-
171
- # ----- Step5: 音声と合成する -----
172
- audio_path = tempfile.NamedTemporaryFile(delete=False, suffix=".wav").name
173
- audio.export(audio_path, format="wav")
174
-
175
- output_path = tempfile.NamedTemporaryFile(delete=False, suffix="_output.mp4").name
176
-
177
- ffmpeg_command = [
178
- "ffmpeg", "-y",
179
- "-i", video_path,
180
- "-i", audio_path,
181
- "-c:v", "copy",
182
- "-c:a", "aac",
183
- output_path
184
- ]
185
- subprocess.run(ffmpeg_command)
186
-
187
- # ----- Step6: Streamlit に動画を表示 -----
188
- st.video(output_path)
189
-
190
- # ----- Cleanup -----
191
- os.remove(video_path)
192
- os.remove(audio_path)
193
- os.remove(output_path)
194
- os.remove(temp_mp3.name)
195
-
196
 
197
  if __name__ == "__main__":
198
  main()
 
1
+ import streamlit as st
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
+ from matplotlib.animation import FuncAnimation
 
 
 
5
  import tempfile
6
  import os
7
  import subprocess
 
 
 
 
 
 
 
 
 
 
8
 
9
+ from pydub import AudioSegment
10
+ from scipy.fft import rfft, rfftfreq
11
+ from scipy.signal import get_window
 
 
 
12
 
13
  def main():
14
+ st.title("フーリエサイケデリックアート")
15
+
16
+ uploaded_file = st.file_uploader("アートに変換する音声ファイルをアップロード (MP3)", type=["mp3"])
17
+
18
+ if uploaded_file is not None:
19
+ st.write("**アップロード完了**。少々お待ちください...")
20
+
21
+ # --- Step 1: MP3 -> WAV 変換 ---
22
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_mp3:
23
+ temp_mp3.write(uploaded_file.read())
24
+ audio = AudioSegment.from_file(temp_mp3.name)
25
+
26
+ # numpy 配列に変換(Mono& 正規化)
27
+ samples = np.array(audio.get_array_of_samples(), dtype=float)
28
+ sample_rate = audio.frame_rate
29
+
30
+ # ステレオの場合はモノラルへ変換
31
+ if audio.channels == 2:
32
+ samples = samples.reshape((-1, 2))
33
+ samples = samples.mean(axis=1)
34
+
35
+ # 整数の場合の最大値で正規化(16bit or 32bitなどに対応)
36
+ samples /= np.max(np.abs(samples))
37
+
38
+ # --- パラメータ ---
39
+ chunk_size = 2048
40
+ overlap = 1024
41
+ step_size = chunk_size - overlap
42
+
43
+ # FFT の準備
44
+ window = get_window("hann", chunk_size)
45
+ freqs = rfftfreq(chunk_size, d=1/sample_rate)
46
+ max_freq = np.max(freqs)
47
+
48
+ # チャンク数の計算
49
+ n_chunks = (len(samples) - chunk_size) // step_size + 1
50
+
51
+ # 各チャンクでの FFT を先にまとめて計算しておく
52
+ fft_frames = []
53
+ for i in range(n_chunks):
54
+ start = i * step_size
55
+ end = start + chunk_size
56
+ chunk = samples[start:end] * window
57
+
58
+ spec = np.abs(rfft(chunk))
59
+ fft_frames.append(spec)
60
+
61
+ # --- Step 2: Matplotlib アニメーションを作成 ---
62
+ fig = plt.figure(figsize=(6, 6))
63
+ ax = plt.subplot(111, projection='polar')
64
+ plt.axis('off') # 軸は消してサイケ感を出す
65
+
66
+ # 初期化用
67
+ theta = freqs / max_freq * 2 * np.pi # 周波数を角度にマッピング
68
+ r_init = fft_frames[0]
69
+ # カラーマップの初期表示
70
+ sc = ax.scatter(theta, r_init, c=r_init, cmap="hsv", alpha=0.7)
71
+
72
+ # 半径方向(Amplitude)の最大値を固定
73
+ ax.set_ylim(0, np.max(fft_frames))
74
+
75
+ # 背景色を黒に
76
+ fig.patch.set_facecolor("black")
77
+ ax.set_facecolor("black")
78
+
79
+ def update(frame):
80
+ """各フレームでの散布図を更新。"""
81
+ r = fft_frames[frame]
82
+ # 散布図を更新 (colors, offsetsなどを更新)
83
+ sc.set_offsets(np.column_stack((theta, r))) # (theta, radius) をまとめる
84
+ sc.set_array(r) # カラーマップ用
85
+ return (sc,)
86
+
87
+ ani = FuncAnimation(fig, update, frames=len(fft_frames), blit=True, interval=50)
88
+
89
+ # --- Step 3: アニメーションを一時的に MP4 に保存 ---
90
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_video:
91
+ ani.save(temp_video.name, fps=30, extra_args=["-vcodec", "libx264"])
92
+ video_path = temp_video.name
93
+
94
+ # --- Step 4: 音声ファイル(WAV) を出力 ---
95
+ audio_path = tempfile.NamedTemporaryFile(delete=False, suffix=".wav").name
96
+ audio.export(audio_path, format="wav")
97
+
98
+ # --- Step 5: FFmpeg で音声と動画を合成 ---
99
+ output_path = tempfile.NamedTemporaryFile(delete=False, suffix="_output.mp4").name
100
+ ffmpeg_command = [
101
+ "ffmpeg", "-y",
102
+ "-i", video_path,
103
+ "-i", audio_path,
104
+ "-c:v", "copy",
105
+ "-c:a", "aac",
106
+ "-strict", "experimental", # 必要に応じて
107
+ output_path
108
+ ]
109
+ subprocess.run(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
110
+
111
+ # --- Step 6: Streamlit で表示 ---
112
+ st.write("**アート生成完了!** 以下の動画お楽しみください。")
113
+ st.video(output_path)
114
+
115
+ # 後片付け
116
+ os.remove(temp_mp3.name)
117
+ os.remove(video_path)
118
+ os.remove(audio_path)
119
+ os.remove(output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
  if __name__ == "__main__":
122
  main()