将 gif 同步到音乐节奏会导致持续时间比预期的要短
Syncing gifs to tempo of music results in shorter duration than expected
我正在尝试将 gif 同步到 Spotify 上播放的音乐节拍,但这样做时遇到了速度问题。我一定要疯了,因为我找不到为什么这不起作用的原因。以下是我的方法:
- 获取初始 BPM(例如:150)并找到 Beats/Second (
BPS
)
BPS = BPM / 60
- 从 Beats/Second (
BPS
) 中找到 Seconds/Beat (SPB
)
SPB = 1 / BPS
- 乘以.gif的Beats/Loop(
BPL
)个数,求Seconds/Loop(SPL
)
SPL = SPB * BPL
- 将 Seconds/Loop (
SPL
) 转换为 Milliseconds/Loop (MSPL
)
MSPL = SPL * 1000
- 将.gif中的Milliseconds/Loop(
MSPL
)除以帧数(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 的实际 Milliseconds/Loop 应该始终等于 MSLP 或大于 MSLP。但是,当我用指定的帧时间保存 .gif 时,如果校正值不为 0,则 .gif 总是以比预期更快的速度播放。我注意到,当使用提供相同 "sync gif to music" 功能的其他在线服务时,情况也是如此;所以我想不只是我发疯了。
下面是用于获取帧时间的实际代码:
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 的实际持续时间比指定的帧时间短得多。其他提供此功能的 sites/services 最终得到相同的结果让我感觉稍微好一些,但同时我觉得这绝对是可能的。
已解决!我不知道这是 .gif 格式的限制还是 PIL 中图像模块的限制,但帧时间似乎只能精确到 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
我正在尝试将 gif 同步到 Spotify 上播放的音乐节拍,但这样做时遇到了速度问题。我一定要疯了,因为我找不到为什么这不起作用的原因。以下是我的方法:
- 获取初始 BPM(例如:150)并找到 Beats/Second (
BPS
)BPS = BPM / 60
- 从 Beats/Second (
BPS
) 中找到 Seconds/Beat (SPB
)SPB = 1 / BPS
- 乘以.gif的Beats/Loop(
BPL
)个数,求Seconds/Loop(SPL
)SPL = SPB * BPL
- 将 Seconds/Loop (
SPL
) 转换为 Milliseconds/Loop (MSPL
)MSPL = SPL * 1000
- 将.gif中的Milliseconds/Loop(
MSPL
)除以帧数(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 的实际 Milliseconds/Loop 应该始终等于 MSLP 或大于 MSLP。但是,当我用指定的帧时间保存 .gif 时,如果校正值不为 0,则 .gif 总是以比预期更快的速度播放。我注意到,当使用提供相同 "sync gif to music" 功能的其他在线服务时,情况也是如此;所以我想不只是我发疯了。
下面是用于获取帧时间的实际代码:
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 的实际持续时间比指定的帧时间短得多。其他提供此功能的 sites/services 最终得到相同的结果让我感觉稍微好一些,但同时我觉得这绝对是可能的。
已解决!我不知道这是 .gif 格式的限制还是 PIL 中图像模块的限制,但帧时间似乎只能精确到 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