使用Python同步音频和动画

4
我已经编写了加载音频文件、计算频谱和动画效果的代码,但我似乎没有使用的工具来将音频与动画进行同步。
我遇到的难题是 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()

请移除背景并直接提出问题。 - Hadij
2
我已经删除了一些无关的文本,但是如果我必须在后面的回答中提供相同的上下文,那么没有上下文的问题是没有帮助的。 - Rob Hilton
2个回答

4

嗯,我找到了一种解决问题的方法。

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

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

杰出的想法! - zero_cool

2

太棒了@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,

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接