「Python|音视频处理|场景案例」如何获取音视频中声音片段的起止时间?

Table of Contents

场景描述

  • 假设我们有一段音频,音频开始有一段无声片段,音频结束也有一段无声片段,我们需要知道开头无声片段的结束位置和结束无声片段的开始位置,或者换句话说,我们需要知道在第几秒开始第一次出现声音,在第几秒位置是最后一次声音,这样就可以去除首末的无声部分。
  • 更进一步,假设我们有一段单词发音音频,整个音频读了多个单词,我们希望得到各个片段的起始时间和结束时间(之后可以根据时间将音频切分为单个单词的发音音频)

我们使用音视频软件打开一段音频,可以看到音频声音结构如下,分为4个有声部分,开头和结束都有一小段无声部分。
在这里插入图片描述

各个音频片段的开始时间和结束时间如下:

音频片段目测开始时间目测结束时间
第一段0.18s4.06s
第二段5.20s5.27s
第三段8.21s9.00s
第四段10.16s18.6s

接下来我们就尝试编写一段程序找出上述四个片段的起止时间

准备工作

  • 处理音视频的时候,可以使用强大的ffmpeg工具完成各种各样的操作
  • python中可以使用底层封装了ffmpeg的第三方库moviepy来快速完成一些常见的音视频处理

所以,我们需要安装ffmpeg(moviepy需要使用),moviepy两个库,

解决方案

想要找出一段音频中在第几秒开始出现声音,我们可以从音频开始以很小的时间间隔检查每一个时间点上的音频音量,如果音频音量等于0(或者小于某个音量值),则认为这个时间点是无声的,检查到第一个不是无声的时间点,就是音频开始出现声音的位置。 同理,如果我们要找出单词带读音频中各个单词的起止时间,则找到第一次出现声音的位置和声音出现后第一次消失的位置就是这个单词音频的起止时间。

我们查找moviepy中是否已经有现成的查看音频音量的方法,可以找到.max_volume()方法可以得到一段音频中的最大音量,使用这个方法,如果一段极短的音频的最大音量是0(或者小于某个值)就认为这个时间段的音频是无声的,则可以设计如下操作:

  • (导入我们要用的moviepy):from moviepy.editor import *
  • 读取音频数据:
    • audio = AudioFileClip("D:/45.mp3")
    • 如果是视频文件, 则使用audio = VideoFileClip("D:/视频文件名.mp4").audio获取视频的音频数据
  • 音频都是从0s开始的,结束时间可以通过audio.end得到
  • 假设我们检查的时间间隔是0.1s,则可以分成audio.end / 0.1个需要检查的时间片段
  • 截取某一个片段的音频可以使用audio_clip = audio.subclip(0, 0.1)
  • 检查获取最大音量:audio_clip.max_volume()

源代码

1import math 2from typing import List 3from moviepy.editor import * 4 5 6def mark_each_duration_sound_or_silent(audio_clip, window_size=0.1, volume_threshold=0.01) -> List[bool]: 7 """标记每一个检查区间的音频片段是有声还是无声""" 8 9 window_amount = math.floor(audio_clip.end / window_size) 10 window_is_silent = [] 11 for i in range(window_amount): 12 s = audio_clip.subclip(i * window_size, (i + 1) * window_size) 13 v = s.max_volume() 14 window_is_silent.append(v < volume_threshold) 15 return window_is_silent 16 17 18def find_sound_appear_and_disappear_position(window_is_silent, window_size=0.1, ease_in=0.25): 19 """找出每一个「无声到有声」和「有声到无声」的时间点作为声音片段的起止时间""" 20 21 speaking_start = 0 22 speaking_end = 0 23 sound_intervals = [] 24 for window_num in range(1, len(window_is_silent)): 25 last_point = window_is_silent[window_num - 1] 26 current_point = window_is_silent[window_num] 27 28 # 出现上一个时间点无声, 当前时间点有声, 当前时间点就是声音开始位置 29 if last_point and not current_point: 30 speaking_start = window_num * window_size 31 32 # 出现上一个时间点有声, 当前时间点无声, 当前时间点就是声音结束位置 33 # 有了声音的开始位置和结束位置,就有了一个声音片段的起止区间 34 if not last_point and current_point: 35 speaking_end = window_num * window_size 36 new_speaking_interval = [speaking_start - ease_in, speaking_end + ease_in] 37 if new_speaking_interval[0] < 0: 38 new_speaking_interval[0] = 0 39 40 # 当时间间隔(window_size)过小而声音渐入区间(ease in)过大时, 会出现时间片段重叠的问题,这种情况需要合并两个区间 41 need_to_merge = len(sound_intervals) > 0 and sound_intervals[-1][1] > new_speaking_interval[0] 42 if need_to_merge: 43 merged_interval = [sound_intervals[-1][0], new_speaking_interval[1]] 44 sound_intervals[-1] = merged_interval 45 else: 46 sound_intervals.append(new_speaking_interval) 47 48 return sound_intervals 49 50 51def find_sound_intervals(audio_clip, window_size=0.1, volume_threshold=0.01, ease_in=0.25): 52 # First, iterate over audio to find all silent windows. 53 window_is_silent = mark_each_duration_sound_or_silent(audio_clip, window_size, volume_threshold) 54 return find_sound_appear_and_disappear_position(window_is_silent, window_size, ease_in) 55 56 57audio = AudioFileClip("D:/45.mp3") 58print(f"Check With Default Options: {find_sound_intervals(audio)}") 59"""输出结果: 60Check With Default Options: [[0.25, 4.3500000000000005], [5.3500000000000005, 6.15], [8.450000000000001, 9.25], [10.25, 18.55]] 61""" 62

找到了各个声音片段的起止时间,我们就可以使用截取音视频的方法将各个片段分别保存到本地磁盘了,快去试试吧~

好书推荐:

好课推荐:

写文不易,如果对你有帮助的话,来一波点赞、收藏、关注吧~👇

Mastodon