在 python 中同步音频和动画

Synchronizing audio and animation in python

我已经编写了代码来加载音频文件、计算频谱并制作动画。我似乎无法使用我正在使用的工具将音频与动画同步。

我遇到的障碍是 pydub 实际上并没有告诉我我在音频中的位置(尽管我可以计时)并且 matplotlib 没有让我控制我在动画中的位置和它没有给我保证的帧速率。

是否有我缺少的技术或工具组合可以让我解决这个特定问题?

代码如下:

from pydub import AudioSegment
from pydub.playback import play
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy import signal
import numpy as np
import threading
import time
from datetime import timedelta

# Load the audio and get the raw data for transformation
sound = AudioSegment.from_mp3("A Day Without Rain - Enya - Flora's Secret.mp3")
sampling_rate = sound.frame_rate
song_length = sound.duration_seconds
left = sound.split_to_mono()[0]
x = left.get_array_of_samples()

# Fourier transform
f, t, Zxx = signal.stft(x, fs=sampling_rate, nperseg=8820, noverlap=5292)
y = np.abs(Zxx.transpose())

# Setup a separate thread to play the music
music_thread = threading.Thread(target=play, args=(sound,))

# Build the figure
fig = plt.figure(figsize=(14, 6))
plt.style.use('seaborn-bright')
ax = plt.axes(xlim=[0, 4000], ylim=[0, 3000])
line1, = ax.plot([], [])


# Matplotlib function to initialize animation
def init():
    global annotation1, annotation2
    line1.set_data([], [])
    annotation1 = plt.annotate("Music: {}".format(""), xy=(0.2, 0.8), xycoords='figure fraction')
    annotation2 = plt.annotate("Animation: {}".format(""), xy=(0.6, 0.8), xycoords='figure fraction')
    return line1,


# Function for the animation
def animate(i):
    global music_start, annotation1, annotation2
    line1.set_data(f, y[i])
    if i == 0:
        music_thread.start()
        music_start = time.perf_counter()
    annotation1.set_text("Music: {}".format(timedelta(seconds=(time.perf_counter() - music_start))))
    annotation2.set_text("Animation: {}".format(timedelta(seconds=i / t.size * song_length)))
    return line1,


anim = FuncAnimation(fig, animate, init_func=init, interval=55)
plt.show()

好吧,我确实找到了解决问题的方法。

事实证明最简单的方法是在设置行数据之前修改动画函数中的帧索引:

i = round((time.perf_counter() - music_start)/song_length * t.size)

耶@Rob Hilton!

非常感谢您发布此问答!对于可能偶然发现这一点并想知道您应该将 Rob 的解决方案放到他的原始代码中的什么位置的其他人,这里是我如何让它工作的。请注意,line1.set_data(f, y[i]) 需要移至 if 语句下方,因为 time.perf_counter() 仅在与其自身的另一个实例相关时起作用。

def animate(i):

global music_start, annotation1, annotation2

if i == 0:
    music_thread.start()
    music_start = time.perf_counter()
    
i = round((time.perf_counter() - music_start)/song_length * t.size)
line1.set_data(f, y[i])

annotation1.set_text("Music: {}".format(timedelta(seconds=(time.perf_counter() - music_start))))
annotation2.set_text("Animation: {}".format(timedelta(seconds=i / t.size * song_length)))
return line1,