その昔、デジタルオーディオプレイヤーというものが存在した。もはや化石とも言えるこの機器を人類で利用しているのは地球上でも数わずか。そんな貴重な旧人類の一人がここにいる。そう、私だ。
体を動かしたりする際にデジタルオーディオプレイヤーを使って音楽はもちろん、ラジオやポッドキャストを聴いたりするのだが、シークがとにかく面倒くさい。物理ボタンを使うのだが数秒単位なので長時間シークすると手がつりそうになる。なにより時間がかかる。そのため、数時間あるポッドキャストの途中でプレイヤーの電池が切れるとげんなりする。
そこで音声ファイルを一定時間ごとに分割することを思いついた。トラックのスキップは一瞬なので特定のポジションまで移動するのに時間がかからない。
分割にはffmpegを使うことにした。ffmpegだと再エンコードを回避できるし(-codec copy
)、指定時間だけ出力対象とすることも可能(-t
)だと知っていた。おそらく開始位置をずらすこともできるだろうと思って調べると、やっぱり出来た。-ss
オプションがそれにあたる。
つまり、ある音声ファイルを先頭10秒後から30秒間切り出すにはこうなる。
ffmepg -ss 10 -i /path/to/input -acodec copy -t 30 /path/to/output
また、分割には総再生時間を知る必要があるが、たいていffmpegと一緒にインストールされているffprobeを使って調べることができる。
ffprobe -print_format json -show_format /path/to/input
これで情報がjson形式でわかる。便利!あとは利用しやすいようにpythonで簡単なスクリプトを書いた。
pythonの実行環境とffmpegがインストールされていてデジタルオーディオプレイヤーを使っている人間は全世界で数人しか居なさそうだが、せっかくなのでcc0で公開しておく。 -d
オプションで分割する秒数を指定する。分割後オリジナルファイルを削除するので注意が必要だ。
#!/usr/bin/env python3
# license: cc0 (https://creativecommons.org/publicdomain/zero/1.0/deed.ja)
from pathlib import Path
import subprocess
import json
import argparse
import platform
import time
class AudioSplitter:
"""音声ファイルを秒数で分割する"""
def __init__(self, win, duration, path):
self.media_length = 0
self.win = win
self.duration = float(duration)
self.path = Path(path)
if not self.path.is_file():
raise Exception('file not found')
def get_media_length(self):
"""mediaの再生時間を取得する"""
if self.media_length:
return self.media_length
else:
media_length = self._get_media_length()
if media_length:
self.media_length = media_length
return media_length
def _get_media_length(self):
if self.win:
bin = "ffprobe.exe"
else:
bin = "ffprobe"
args = [bin, '-hide_banner', '-print_format', 'json', '-show_format', '-loglevel', 'quiet', self.path]
comp = subprocess.run(args, stdout=subprocess.PIPE, check=True)
output = comp.stdout
parsed = json.loads(output)
return float(parsed["format"]["duration"])
def real_split(self, duration, start, output):
if self.win:
bin = 'ffmpeg.exe'
else:
bin = 'ffmpeg'
args = [bin, '-hide_banner', '-y', '-ss', str(start), '-i', str(self.path), '-map', '0:a', '-acodec', 'copy', '-vn', '-map_metadata', '-1', '-t', str(duration), str(output)]
comp = subprocess.run(args, check=True)
def split(self):
length = self.get_media_length()
current = 0
index = 0
original = self.path.name
duration = self.duration
while length > current:
start = current
index = index + 1
filename = f'{index}_{original}'
output = self.path.with_name(filename)
self.real_split(self.duration, start, output)
current = current + duration
else:
self.path.unlink()
def argparser():
parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='+', help='input file path')
parser.add_argument('-d', '--duration', default='900', help='ファイルあたりの最大時間')
#parser.add_argument('-w', '--windows', action='store_true')
args = parser.parse_args()
return args
def main():
args = argparser()
if platform.system() == 'Windows':
win = True
elif str(Path.cwd()).startswith('/mnt/c/'):
win = True
else:
win = False
for f in args.files:
audio = AudioSplitter(win=win, duration=args.duration, path=f)
audio.split()
time.sleep(1)
if __name__ == '__main__':
main()