Closed10
pydubで無音時間をカットする
シンプルにやるならこれだけ
from pydub import AudioSegment
from pydub.silence import split_on_silence
sound = AudioSegment.from_file("sample.mp3", "mp3")
chunks = split_on_silence(sound,
min_silence_len = 100,
silence_thresh = -35,
keep_silence = 100)
cutted_sound = sum(chunks)
cutted_sound.export('cutted.mp3')
手元のソースで試してみると結構喋ってる部分もカットされてた。音源にもよるのだろうと思う(今回使った音源はちょっと不明瞭な部分が多い)。ソースによってパラメータは色々調整したほうが良さそう。
Whisperに食わせてみたんだけど、いい感じに認識してくれなくて、上に書いたとおり音源そのものの品質は重要な気がしてる。
あとはaudacityとかこれとかで加工するとか
こういうのがあるのね、面白そう
とあるm4a音源を無音化&分割するのに、パラメータ試行錯誤するためのスクリプト
import os
import subprocess
import sys
from pydub import AudioSegment
from pydub.silence import split_on_silence
input_file = "sample.m4a"
sampling_rate = 44100
bit_rate = 128
tempo = 0.9
output_file = "output.mp3"
min_silence_len = 100
keep_silence = 100
silence_thresh = -35
if not os.path.isfile(output_file):
cmd = "ffmpeg -y -i {} -ar {} -ab {}k -af atempo={} {}".format(
input_file, sampling_rate, bit_rate, tempo, output_file
)
resp = subprocess.check_output(cmd, shell=True)
sound = AudioSegment.from_file("output.mp3", "mp3")
org_length = len(sound)
print("original length: {:.2f} 分".format(org_length / 1000 / 60))
chunks = split_on_silence(
sound,
min_silence_len=min_silence_len,
silence_thresh=silence_thresh,
keep_silence=keep_silence,
)
num_of_chunks = len(chunks)
result_length = 0
for i, c in enumerate(chunks):
result_length += len(c)
# print("{}: {:.2f} 秒".format(i, len(c) / 1000))
print("number of chunks: {}".format(num_of_chunks))
print(
"result length: {:.2f} 分 ( {:.2f} 分 cutted)".format(
result_length / 1000 / 60, (org_length - result_length) / 1000 / 60
)
)
print("avg chunk length: {:.2f} 秒".format(result_length / num_of_chunks / 1000))
結果
$ time python app.py
original length: 93.94 分
number of chunks: 6239
result length: 65.34 分 ( 28.60 分 cutted)
avg chunk length: 0.63 秒
real 2m15.049s
user 2m14.811s
sys 0m6.755s
Whisperで学習させれるように、30分ごとにファイルに分けるとこんな感じ。
import os
import subprocess
import sys
from pydub import AudioSegment
from pydub.silence import split_on_silence
input_file = "sample.m4a"
sampling_rate = 44100
bit_rate = 128
tempo = 0.9
output_file = "output.mp3"
args = sys.argv
min_silence_len = 200
keep_silence = 200
silence_thresh = -90
if not os.path.isfile(output_file):
cmd = "ffmpeg -y -i {} -ar {} -ab {}k -af atempo={} {}".format(
input_file, sampling_rate, bit_rate, tempo, output_file
)
resp = subprocess.check_output(cmd, shell=True)
sound = AudioSegment.from_file("output.mp3", "mp3")
org_length = len(sound)
print("original length: {:.2f} 分".format(org_length / 1000 / 60))
chunks = split_on_silence(
sound,
min_silence_len=min_silence_len,
silence_thresh=silence_thresh,
keep_silence=keep_silence,
)
num_of_chunks = len(chunks)
result_length = 0
def output_wav(chunk, output):
chunk.export(output, format="wav")
s = AudioSegment.from_file(output, "wav")
print("{}: {:.2f} 秒".format(output, s.duration_seconds))
current_chunk = None
for i, c in enumerate(chunks):
if current_chunk is None:
current_chunk = c
continue
temp_chunk = current_chunk + c
outFilePath = f"output/out_{i + 1}.wav"
if len(temp_chunk) / 1000 > 30:
output_wav(current_chunk, outFilePath)
current_chunk = c
else:
if i == len(chunks) - 1:
output_wav(temp_chunk, outFilePath)
else:
current_chunk += c
result_length += len(c)
# print("{}: {:.2f} 秒".format(i, len(c) / 1000))
print("number of chunks: {}".format(num_of_chunks))
print(
"result length: {:.2f} 分 ( {:.2f} 分 cutted)".format(
result_length / 1000 / 60, (org_length - result_length) / 1000 / 60
)
)
print("avg chunk length: {:.2f} 秒".format(result_length / num_of_chunks / 1000))
$ time python app.py
original length: 93.94 分
output/out_8.wav: 22.55 秒
output/out_14.wav: 28.18 秒
output/out_23.wav: 28.67 秒
output/out_38.wav: 27.69 秒
output/out_52.wav: 26.40 秒
output/out_65.wav: 27.99 秒
output/out_70.wav: 29.22 秒
output/out_74.wav: 19.08 秒
output/out_79.wav: 29.30 秒
output/out_85.wav: 24.46 秒
output/out_90.wav: 20.86 秒
output/out_94.wav: 22.63 秒
output/out_97.wav: 23.55 秒
output/out_103.wav: 29.20 秒
output/out_111.wav: 27.98 秒
output/out_119.wav: 27.25 秒
output/out_125.wav: 25.06 秒
output/out_128.wav: 22.07 秒
output/out_132.wav: 26.85 秒
output/out_140.wav: 29.34 秒
output/out_149.wav: 27.30 秒
output/out_157.wav: 25.64 秒
output/out_163.wav: 29.63 秒
output/out_168.wav: 20.14 秒
output/out_174.wav: 24.20 秒
output/out_177.wav: 25.96 秒
output/out_184.wav: 27.61 秒
output/out_188.wav: 24.60 秒
output/out_196.wav: 29.16 秒
output/out_201.wav: 27.47 秒
output/out_207.wav: 29.28 秒
output/out_221.wav: 26.27 秒
output/out_225.wav: 17.82 秒
output/out_230.wav: 29.49 秒
output/out_234.wav: 15.76 秒
output/out_236.wav: 18.90 秒
output/out_240.wav: 26.76 秒
output/out_247.wav: 28.93 秒
output/out_254.wav: 27.94 秒
output/out_259.wav: 29.92 秒
output/out_270.wav: 29.58 秒
output/out_277.wav: 28.18 秒
output/out_282.wav: 25.20 秒
output/out_290.wav: 28.65 秒
output/out_301.wav: 27.89 秒
output/out_313.wav: 28.98 秒
output/out_322.wav: 26.36 秒
output/out_328.wav: 29.24 秒
output/out_333.wav: 24.19 秒
output/out_339.wav: 26.81 秒
output/out_348.wav: 28.30 秒
output/out_358.wav: 28.36 秒
output/out_370.wav: 29.32 秒
output/out_377.wav: 24.44 秒
output/out_387.wav: 29.24 秒
output/out_398.wav: 25.75 秒
output/out_404.wav: 28.68 秒
output/out_414.wav: 29.85 秒
output/out_419.wav: 27.22 秒
output/out_432.wav: 29.50 秒
output/out_440.wav: 23.80 秒
output/out_447.wav: 28.40 秒
output/out_452.wav: 28.62 秒
output/out_461.wav: 28.01 秒
output/out_465.wav: 29.25 秒
output/out_472.wav: 29.79 秒
output/out_481.wav: 28.17 秒
output/out_490.wav: 26.49 秒
output/out_494.wav: 28.54 秒
output/out_506.wav: 27.40 秒
output/out_515.wav: 23.88 秒
output/out_521.wav: 29.37 秒
output/out_527.wav: 21.29 秒
output/out_536.wav: 27.52 秒
output/out_542.wav: 29.07 秒
output/out_555.wav: 29.55 秒
output/out_561.wav: 21.80 秒
output/out_568.wav: 29.94 秒
output/out_577.wav: 29.69 秒
output/out_584.wav: 26.44 秒
output/out_592.wav: 24.42 秒
output/out_598.wav: 28.46 秒
output/out_606.wav: 29.47 秒
output/out_610.wav: 12.85 秒
output/out_614.wav: 29.58 秒
output/out_621.wav: 26.54 秒
output/out_627.wav: 29.50 秒
output/out_633.wav: 27.81 秒
output/out_643.wav: 29.42 秒
output/out_654.wav: 29.09 秒
output/out_662.wav: 11.06 秒
output/out_664.wav: 24.67 秒
output/out_667.wav: 28.22 秒
output/out_673.wav: 27.17 秒
output/out_678.wav: 24.55 秒
output/out_684.wav: 28.07 秒
output/out_691.wav: 28.16 秒
output/out_697.wav: 22.08 秒
output/out_701.wav: 29.21 秒
output/out_709.wav: 18.01 秒
output/out_714.wav: 28.40 秒
output/out_724.wav: 26.51 秒
output/out_727.wav: 28.37 秒
output/out_738.wav: 29.53 秒
output/out_746.wav: 27.25 秒
output/out_750.wav: 18.00 秒
output/out_754.wav: 22.39 秒
output/out_760.wav: 26.03 秒
output/out_764.wav: 23.31 秒
output/out_773.wav: 25.50 秒
output/out_776.wav: 20.93 秒
output/out_782.wav: 28.55 秒
output/out_790.wav: 27.47 秒
output/out_798.wav: 26.95 秒
output/out_808.wav: 27.68 秒
output/out_813.wav: 29.69 秒
output/out_820.wav: 27.93 秒
output/out_829.wav: 22.72 秒
output/out_834.wav: 25.10 秒
output/out_846.wav: 27.81 秒
output/out_852.wav: 22.04 秒
output/out_857.wav: 22.94 秒
output/out_862.wav: 26.65 秒
output/out_869.wav: 25.50 秒
output/out_876.wav: 28.83 秒
output/out_882.wav: 29.11 秒
output/out_890.wav: 29.71 秒
output/out_896.wav: 24.70 秒
output/out_903.wav: 29.57 秒
output/out_911.wav: 19.05 秒
output/out_919.wav: 27.17 秒
output/out_927.wav: 27.79 秒
output/out_936.wav: 29.89 秒
output/out_949.wav: 29.37 秒
output/out_960.wav: 28.01 秒
output/out_965.wav: 29.61 秒
output/out_974.wav: 28.61 秒
output/out_984.wav: 29.90 秒
output/out_994.wav: 28.46 秒
output/out_1005.wav: 26.06 秒
output/out_1013.wav: 29.81 秒
output/out_1020.wav: 23.66 秒
output/out_1027.wav: 29.96 秒
output/out_1033.wav: 29.65 秒
output/out_1045.wav: 24.91 秒
output/out_1050.wav: 24.46 秒
output/out_1055.wav: 26.98 秒
output/out_1063.wav: 29.78 秒
output/out_1074.wav: 28.51 秒
output/out_1084.wav: 25.34 秒
output/out_1095.wav: 29.61 秒
output/out_1106.wav: 29.21 秒
output/out_1111.wav: 23.56 秒
output/out_1114.wav: 21.89 秒
output/out_1120.wav: 28.09 秒
output/out_1125.wav: 27.42 秒
output/out_1132.wav: 26.65 秒
output/out_1142.wav: 23.88 秒
output/out_1148.wav: 28.92 秒
output/out_1157.wav: 29.34 秒
output/out_1167.wav: 29.78 秒
output/out_1179.wav: 29.09 秒
output/out_1190.wav: 27.23 秒
output/out_1195.wav: 29.14 秒
output/out_1207.wav: 29.58 秒
output/out_1213.wav: 28.59 秒
output/out_1223.wav: 28.96 秒
output/out_1227.wav: 20.07 秒
output/out_1233.wav: 27.47 秒
output/out_1244.wav: 28.57 秒
output/out_1250.wav: 21.65 秒
output/out_1254.wav: 25.33 秒
output/out_1260.wav: 27.07 秒
output/out_1270.wav: 28.40 秒
output/out_1276.wav: 13.72 秒
output/out_1278.wav: 26.64 秒
output/out_1285.wav: 25.61 秒
output/out_1290.wav: 24.97 秒
output/out_1296.wav: 22.55 秒
output/out_1306.wav: 28.02 秒
output/out_1311.wav: 29.85 秒
output/out_1320.wav: 23.07 秒
output/out_1323.wav: 28.92 秒
output/out_1331.wav: 26.12 秒
output/out_1339.wav: 26.03 秒
output/out_1348.wav: 29.80 秒
output/out_1357.wav: 29.73 秒
output/out_1365.wav: 27.91 秒
output/out_1378.wav: 29.91 秒
number of chunks: 1378
result length: 83.69 分 ( 10.25 分 cutted)
avg chunk length: 3.64 秒
real 3m46.881s
user 3m32.862s
sys 0m7.799s
Whisper APIが出た
入力は最大25MBまでということで、無音部分を削除しつつ25MBに収まるようファイルを分割出力するようにしてみた。
import os
import subprocess
import sys
import pydub
from pydub import AudioSegment
from pydub.silence import split_on_silence
src_file = "sample.m4a"
work_dir = "work"
os.makedirs(work_dir, exist_ok=True)
max_file_size = 25 * 1000 * 1000 * 0.9 # Whisper APIに送信できるのは25MBまで。10%ほどブレを見込んでおく。
# split_on_silenceで分割する際のパラメータ。適宜調整。
min_silence_len = 500
keep_silence = 500
silence_thresh = -90
# mp3以外はmp3に変換する
if os.path.splitext(src_file)[-1] != ".mp3":
sampling_rate = 44100
bit_rate = 128
output_file = os.path.splitext(src_file)[0] + ".mp3"
cmd = "ffmpeg -y -i {} -ar {} -ab {}k {}".format(
src_file, sampling_rate, bit_rate, output_file
)
resp = subprocess.check_output(cmd, shell=True)
src_file = output_file
sound = AudioSegment.from_file(src_file, format="mp3")
total_length = len(sound)
total_size = os.path.getsize(src_file)
max_length = total_length * (max_file_size / total_size) # ファイルサイズと時間から、分割する最大時間を取得する
# 無音部分をカットして分割
chunks = split_on_silence(
sound,
min_silence_len=min_silence_len,
silence_thresh=silence_thresh,
keep_silence=keep_silence,
)
num_of_chunks = len(chunks)
def output_mp3(chunk, output):
chunk.export(output, format="mp3")
s = AudioSegment.from_file(output, "mp3")
# 分割したチャンクをmax_lengthごとに結合
current_chunk = None
for i, c in enumerate(chunks):
if current_chunk is None:
current_chunk = c
continue
temp_chunk = current_chunk + c
outFilePath = f"{work_dir}/out_{i + 1}.mp3"
if len(temp_chunk) > max_length:
output_mp3(current_chunk, outFilePath)
current_chunk = c
else:
if i == len(chunks) - 1:
output_mp3(temp_chunk, outFilePath)
else:
current_chunk += c
APIのレスポンスはかなり速いらしいんだけど、ffmpegでmp3に変換してpydubのsplit_on_silenceで無音カット&分割で時間かかるのはちょっとつらいなー。ただ分割しないといけないような場合はそれほど即時性はいらないのかもだけど。
元ファイルの中身にもよるけどsplit_on_silenceのパラメータは大きめに取るほうがいいかも。文の途中で切らないほうがいいみたいなので。
全然関係ないけど、こういうのを事前に通しておくと良さそう。
このスクラップは2023/06/30にクローズされました