python提取时长2s以内的单词音频的韵母基频,以及单词词长信息
提取信息自动存入当前工作空间中的excel文件,包括文件名、前字时长、后字时长、两字总时长、前字韵母基频、后字韵母基频。(10个点,9段,采用自相关法)
音频源文件为.wav和.textgrid文件。如gong1_yuan2.wav、gong1_yuan2.textgrid,其中gong1_yuan2代表单词“公园”,1、2代表声平前字为1声,后字为2声。
使用代码时,文件夹格式建议如下,要有M001这个二级目录。(M为male性别,001代表说话人编号。)若你的代码无男女区别,则也建议新建一个M001子目录。
参考数据链接:
https://pan.baidu.com/s/1Sdymm3XYD9uXKGoD8UYoDg
提取码:d851
如需要多次处理音频,将旧音频文件删除,复制新的进去就行。
基本算法思想
根据textgrid文件标注的单词的韵头、韵母、以及中间silence段的时间序列,计算出每个韵头、韵母左右端点。在textgrid文件中,以gong1_yuan2.textgrid为例,前后字的Intervals为 ( 0.5,1.0,T1) 、( 1.,2,1.9,T12。 韵头为(0.5,0.6,g),韵母为(0.6,1.0,ong),其他依次类推。两字之间可能存在silence段:(1.0,1.2,sil)。
关于Textgrid文件格式的参考:https://blog.csdn.net/duxin_csdn/article/details/88966295
根据文件采样频率计算出离散时间序列横坐标,根据韵头韵母的左右端点,计算出时间序列横坐标区间,即为韵头or韵母所在的区间。 如时间序列为 0.1 0.2 0.3 0.4 0.5,韵母左端点标注为0.25,则取左区间端点为0.3.其他同理。
根据时间区间即可得到对应数字信号序列,将其输入自相关算法即可计算基频。
若需要提取韵头基频,根据算法修改即可。本文所建立的韵母、韵头表,未必对所有使用者的textgrid的标注都适用。
import textgrid
import xlwt
import os
import re
import wave
import numpy as np
import math
#gong1_yuan2.TextGrid
from matplotlib import pyplot as plt
import scipy.io.wavfile as wav
book = xlwt.Workbook() # 创建Workbook,相当于创建Excel encoding='utf-8'
# 创建sheet,Sheet1为表的名字,cell_overwrite_ok为是否覆盖单元格
sheet1 = book.add_sheet('Sheet1')
D1=D2=D3=D4=0
def getparam(file_name):
#此函数获取2个单字的时长、总长、第一个字韵母时间区间(D1,D2)、第二个字韵母时间区间 (D3,D4)
#第一个单字时间总长T1 第二个单字时间总长T2 ,全长时间T3
tg = textgrid.TextGrid()
tg.read(file_name) # 'file.TextGrid' 是文件名
sylset = {'b', 'p', 'm', 'f', 'd', 't', 'n', 'l', 'g', 'k', 'h', 'j', 'q', 'x', 'zh', 'ch', 'sh', 'z', 'c', 's',
'y', 'w', 'r','e'}
sylmset={'a','ai','an','ang','ao','e','ei','en','eng','er','o','ong','ou','iu','i','ia','ian','iang','iao','ie','in','ing','iong','iou','u','un','ui','ua','uai','uan','uang','uei','uen','ueng','uo','v','van','ve','vn','R','iii','ii','eR'}
#根据文件名处理标记
twonum=re.findall("\d+", file_name)
#'1'代表韵头,'0'代表韵母
syltag=''
num=1
b = 1
a = [] # 韵母区间
c = [] # 韵母标记 注意可能出现3个韵母,则有6个区间数字,需要修改成4个
for i in range(0, len(tg.tiers)):
for j in range(0, len(tg.tiers[i])):
##############################################求韵母区间##############################################
if (((tg.tiers[i][j].mark != 'None') & (tg.tiers[i][j].mark != 'sil') & (tg.tiers[i][j].mark != ''))):
if (tg.tiers[i][j].mark in sylmset ):
a.append(tg.tiers[i].intervals[j].minTime)
a.append(tg.tiers[i].intervals[j].maxTime)
c.append(tg.tiers[i].intervals[j].mark)
#############################################↑↑↑求韵母区间↑↑↑##############################################
##############################################求词时长##############################################
if (twonum[0] != twonum[1]):
if ((tg.tiers[i][j].mark == 'T' + twonum[0])):
T1time = round(tg.tiers[i].intervals[j].maxTime - tg.tiers[i].intervals[j].minTime, 5) # 第一个词时长
A1 = round(tg.tiers[i].intervals[j].minTime,5)
if ((tg.tiers[i][j].mark == 'T' + twonum[1])):
T2time = round(tg.tiers[i].intervals[j].maxTime - tg.tiers[i].intervals[j].minTime, 5) # 第2个词时长
A2 = round(tg.tiers[i].intervals[j].maxTime,5)
else:
if ((tg.tiers[i][j].mark == 'T' + twonum[0])):
if (b == 1):
T1time = round(tg.tiers[i].intervals[j].maxTime - tg.tiers[i].intervals[j].minTime, 5) # 第一个词时长
A1=round(tg.tiers[i].intervals[j].minTime,5)
b = b + 1
else:
T2time = round(tg.tiers[i].intervals[j].maxTime - tg.tiers[i].intervals[j].minTime, 5) # 第2个词时长
A2 = round(tg.tiers[i].intervals[j].maxTime,5)
##############################################↑↑↑求词时长↑↑↑##############################################
if(len(a)>4 and c[0]=='e'):
a.pop(0)
a.pop(0)
else:
if(len(a)>4 and c[1]=='e'):
a.pop(2)
a.pop(2)
D1 = a[0]
D2 = a[1]
D3 = a[2]
D4 = a[3]
T3time = A2 - A1
print( str(T1time)+'---'+ str(T2time)+'---'+str(T3time)+'-/-/D1:'+str(D1)+'---D2:'+str(D2)+'--//-D3:'+str(D3)+'---D4:'+str(D4) )
return [T1time,T2time,T3time,D1,D2,D3,D4]
def write_file(file_name,file_name_wav,file_pre,j):
#此函数将第j个文件写入excel第j行 (从0开始数
par=getparam(file_name)
#向表中添加数据
sheet1.write(j, 0, file_pre[0]+'/'+file_name.split('.',1)[0]) #写入第一列文件名
#sheet1.write(j, 8,file_pre ) # 写入第一列文件名
for i in range(0,len(par)):
sheet1.write(j, i+1, str(par[i])) #写入时间数据。
# 获取10个点的基频。根据time数据和wavedata数据.
wav_data, time,fs = read_wav_data(file_name_wav)
# 找到大于D1 小于D2的点。为前字区间。命名为L1,L2。
# 找到大于D1的点。
L1=0
L2=0
L3=0
L4=0
for i in range(0, len(wav_data), 1):
if (time[i] >par[3]):#D1)
L1 = i;
break
# 找到小于D2的点。
for i in range(0, len(wav_data), 1):
if (time[i] >par[4]):
L2 = i - 1;
break
# 找到大于D3 小于D4的点。为前字区间。命名为L3,L4。
# 找到大于D3的点。
for i in range(0, len(wav_data), 1):
if (time[i] >par[5]):
L3 = i;
break
# 找到小于D2的点。
for i in range(0, len(wav_data), 1):
if (time[i] >par[6]):
L4 = i - 1;
break
# 在L1--L2 L3--L4之间取10个点。
if(L3>L2):
global fre1
fre1 = np.arange(0, 10, 1)
global fre2
fre2 = np.arange(0, 10, 1)
m1=0;
m2=0;
for i in range(L1, L2, math.ceil((L2 - L1 + 1)/10)):
k=i+math.ceil((L2 - L1 + 1)/10)
fre1[m1]=find_pitch(wav_data[i:k],fs)
m1=1+m1
if(m1>=9):
break
for i in range(L3, L4, math.ceil((L4 - L3 + 1) / 10)):
k = i + math.ceil((L4 - L3 + 1) / 10)
fre2[m2] = find_pitch(wav_data[i:k],fs)
m2 = 1 + m2
if (m2 >= 9):
break
# 将基频写入
for i in range(9, 18):
sheet1.write(j, i, str(fre1[i-9]))
for i in range(20, 29):
sheet1.write(j, i, str(fre2[i-20]))
print(file_pre[0] + '/' + file_name.split('.', 1)[0] + '->>>' + str(L1) + '---' + str(L2) + '---' + str(
L3) + '---' + str(L4) +'->par5==' + str(par[3]) + '->par6==' + str(par[4]) +'->par5==' + str(par[5]) + '->par6==' + str(par[6]))
#print(fre1)
book.save('adata1.xls')
def file_name(file_dir):
#此函数从路径下文件中获取所有textgrid文件的名字
L=[]
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.TextGrid':
L.append(os.path.join(root, file))
for i in range(0,len(L)):
L[i]=os.path.basename(L[i])
return L
def file_name_wave(file_dir):
# 此函数从路径下文件中获取所有wave文件的名字
L=[]
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.wav':
L.append(os.path.join(root, file))
for i in range(0,len(L)):
L[i]=os.path.basename(L[i])
return L
def file_pre_name(file_dir):
# 此函数从路径下文件中获取所有文件的上级目录名
L=[]
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.wav':
L.append(os.path.join(root, file))
for i in range(0,len(L)):
L[i]=re.findall("\w\d\d\d",L[i])
return L
def read_wav_data(filename):
fs, wavdata = wav.read(filename)
time=np.arange(0,len(wavdata))/fs
return wavdata,time,fs
def find_pitch(x,fs):
ms20 = int((fs / 50))
ms2 = int(fs / 500)
#print('--ms20='+str(ms20)+'--ms2='+str(ms2)+'--fs='+str(fs))
x = [i / 32767 for i in x]
y = plt.acorr(x, maxlags=110, normed=True)
y = y[1]
z = y[round(len(y) / 2):]
z = z[ms2:ms20]
zmax = max(z)
index = np.where(z == zmax)
index = index[0][0]
pitch = fs / (ms2 + index + 2)
#plt.show()
return pitch
# 下面是自己的文件夹地址
FileNameset = file_name(r'F:\PythonVirtualEnv\wuxiaoxin_para\音频')
FileNameset_wave = file_name_wave(r'F:\PythonVirtualEnv\wuxiaoxin_para\音频')
FileNameset_pre=file_pre_name(r'F:\PythonVirtualEnv\wuxiaoxin_para\音频')
# excel第一行
markname = ['文件の名', '首字时长', '次字时长', '两字总时长', '首字韵母左端点', '首字韵母右端点', '次字韵母左端点', '次字韵母右端点']
for i in range(0, len(markname)):
sheet1.write(0, i, markname[i])
sheet1.write(0, 9, '前字基频')
sheet1.write(0, 20,'后字基频')
# 将T1time至D4数据输入excel
for i in range(0, len(FileNameset)):
write_file(FileNameset[i],FileNameset_wave[i],FileNameset_pre[i], i+1)
if(i==len(FileNameset)-1):
print('process finished')
#write_file('ban1_fa1.TextGrid','ban1_fa1.wav', 2)