Closed10

pydubで無音時間をカットする

kun432kun432

シンプルにやるならこれだけ

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')
kun432kun432

手元のソースで試してみると結構喋ってる部分もカットされてた。音源にもよるのだろうと思う(今回使った音源はちょっと不明瞭な部分が多い)。ソースによってパラメータは色々調整したほうが良さそう。

kun432kun432

Whisperに食わせてみたんだけど、いい感じに認識してくれなくて、上に書いたとおり音源そのものの品質は重要な気がしてる。

kun432kun432

とある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
kun432kun432

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
kun432kun432

Whisper APIが出た

https://openai.com/blog/introducing-chatgpt-and-whisper-apis

入力は最大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にクローズされました