python - 将 GIF 与音乐节奏同步导致持续时间比预期的要短

标签 python audio gif

我正在尝试将 gif 同步到 Spotify 上播放的音乐节拍,但这样做时遇到了速度问题。我一定要疯了,因为我找不到为什么这不起作用的原因。以下是我的方法:

  • 获取初始 BPM(例如:150)并找到 Beats/Second(BPS)
  • BPS = BPM / 60
  • 从 Beats/Second ( SPB ) 中找到 Seconds/Beat ( BPS )
  • SPB = 1 / BPS
  • 通过乘以 .gif 的节拍/循环 (SPL) 的数量来找到秒/循环 (BPL)
  • SPL = SPB * BPL
  • 将秒/循环 (SPL) 转换为毫秒/循环 (MSPL)
  • MSPL = SPL * 1000
  • 将毫秒/循环 ( MSPL ) 除以 .gif 中的帧数 ( num_frames ) 以找到一帧所需的时间 ( frame_time ),四舍五入到最接近的偶数,因为 .gif 帧时间是只精确到整毫秒
  • frame_time = MSPL / num_frames
  • 将总帧时间 ( actual_duration ) 相加并循环帧加或减 1 毫秒,直到 actual_duration匹配 ceil(MSPL) (始终优先考虑较长的实际持续时间而不是较短的持续时间)

    difference = MSPL - actual_duration
    if not math.isclose(0, difference):
        # Add the difference and always prioritize longer duration compared to real duration value
        correction = int(math.ceil(difference))
        for i in range(0, abs(correction)):
            # Add/subtract corrections as necessary to get actual duration as close as possible to calculated duration
            frame_times[i % len(frame_times)] += math.copysign(1, correction)
    

  • 现在从这一点来看,gif 的实际毫秒/循环应该始终等于 MSLP 或大于 MSLP。但是,当我使用指定的帧时间保存 .gif 时,如果校正值不为 0,则 .gif 始终以比预期更快的速度播放。我注意到,当使用提供相同“将 gif 同步到音乐”功能的其他在线服务时,情况也是如此;所以我认为不仅仅是我发疯了。

    下面是用于获取帧时间的实际代码:

    def get_frame_times(tempo: float, beats_per_loop: int, num_frames: int):
        # Calculate the number of seconds per beat in order to get number of milliseconds per loop
        beats_per_sec = tempo / 60
        secs_per_beat = 1 / beats_per_sec
        duration = math.ceil(secs_per_beat * beats_per_loop * 1000)
        frame_times = []
        # Try to make frame times as even as possible by dividing duration by number of frames and rounding
        actual_duration = 0
        for _ in range(0, num_frames):
            # Rounding method: Bankers Rounding (round to the nearest even number)
            frame_time = round(duration / num_frames)
            frame_times.append(frame_time)
            actual_duration += frame_time
        # Add the difference and always prioritize longer duration compared to real duration value
        difference = duration - actual_duration
        if not math.isclose(0, difference):
            correction = int(math.ceil(difference))
            for i in range(0, abs(correction)):
                # Add/subtract corrections as necessary to get actual duration as close as possible to calculated duration
                frame_times[i % len(frame_times)] += math.copysign(1, correction)
        return frame_times
    

    我正在使用 PIL (Pillow) 的图像模块保存 gif:

    frame_times = get_frame_times(tempo, beats_per_loop, num_frames)
    frames = []
    for i in range(0, num_frames):
      # Frames are appended to frames list here
    # disposal=2 used since the frames may be transparent
    frames[0].save(
        output_file, 
        save_all=True, 
        append_images=frames[1:], 
        loop=0, 
        duration=frame_times, 
        disposal=2)
    

    我在这里做错了什么吗?我似乎无法找出为什么这不起作用以及为什么 gif 的实际持续时间比指定的帧时间短得多。提供此功能的其他站点/服务最终得到相同的结果让我感觉稍微好一些,但同时我觉得这绝对应该是可能的。

    最佳答案

    解决了!我不知道这是 .gif 格式的限制,还是 PIL 中 Image 模块的限制,但似乎帧时间只能精确到 10 毫秒的倍数。在检查修改后的图像的实际帧时间时,它们被限制到最接近的 10 倍数,从而导致整体播放速度比预期的要快。

    为了解决这个问题,我修改了代码以选择以 10 为增量的帧时间(如果需要,再次优先考虑更长的实际持续时间)并在整个列表中尽可能均匀地分散帧时间调整:

    def get_frame_times(tempo: float, beats_per_loop: int, num_frames: int):
        # Calculate the number of seconds per beat in order to get number of milliseconds per loop
        beats_per_sec = tempo / 60
        secs_per_beat = 1 / beats_per_sec
        duration = round_tens(secs_per_beat * beats_per_loop * 1000)
        frame_times = []
        # Try to make frame times as even as possible by dividing duration by number of frames
        actual_duration = 0
        for _ in range(0, num_frames):
            frame_time = round_tens(duration / num_frames)
            frame_times.append(frame_time)
            actual_duration += frame_time
        # Adjust frame times to match as closely as possible to the actual duration, rounded to multiple of 10
        # Keep track of which indexes we've added to already and attempt to split corrections as evenly as possible
        # throughout the frame times
        correction = duration - actual_duration
        adjust_val = int(math.copysign(10, correction))
        i = 0
        seen_i = {i}
        while actual_duration != duration:
            frame_times[i % num_frames] += adjust_val
            actual_duration += adjust_val
            if i not in seen_i:
                seen_i.add(i)
            elif len(seen_i) == num_frames:
                seen_i.clear()
                i = 0
            else:
                i += 1
            i += num_frames // abs(correction // 10)
        return frame_times
    

    关于python - 将 GIF 与音乐节奏同步导致持续时间比预期的要短,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62136055/

    相关文章:

    java背景透明

    python - ScraPy蜘蛛爬行但不导出

    python - 我有一个 append 到列表的输入,如何打印出最后一个列表项(最后一个输入)?

    python - 如何对 Pandas 的多索引进行分组?

    c++ - 改变音量 win32 c++

    ios - 将 uiview 捕获为图像

    go - 编码/解码 Gif

    Python 请求模块 : urlencoding json data

    ios - 在手机上进行第二次音频录制后未触发SampleDataEvent

    redirect - Oculus Rift SDK 2更改旋转增益