動機
あるフォルダの中に複数の動画ファイルがあり、その再生時間の合計を取得する必要があったので nushell を使って取得してみました。
環境
Windows 11 24H2
nushell 0.108.0
ffmpeg version 8.0-full_build-www.gyan.dev
解説
nushell とは
JSON や CSV のような構造化されたデータをパイプラインで扱えるのが特徴のシェルです。従来のシェルならあるコマンドの出力を正規表現でパースして…なんてことをやると思いますが、nushell は様々なデータ形式に対応しているのでデータの扱いが簡単になります。
ffprobe とは
ffmpeg に付属のツールで、動画ファイルなどの情報を収集し読み取りやすいデータにして出力してくれます。JSON にも対応しているので、その出力を nushell に渡します。
下記のコマンドを実行すると JSON 形式で動画の情報が出力されます。
> ffprobe -v quiet -show_format -show_streams -print_format json /path/to/movie.mp4
{
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
"profile": "High",
"codec_type": "video",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 1920,
"height": 1080,
// 省略
},
{
"index": 1,
"codec_name": "aac",
"codec_long_name": "AAC (Advanced Audio Coding)",
"profile": "LC",
"codec_type": "audio",
// 省略
}
],
"format": {
"filename": "movie.mp4",
// 省略
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"format_long_name": "QuickTime / MOV",
"start_time": "0.000000",
"duration": "101.358345",
"size": "217176161",
"bit_rate": "17141255",
// 省略
}
nushell で JSON をパースする
nushell のfrom jsonというコマンドにパイプラインで JSON の文字列を渡すだけです。nushell では構造化されたデータは下記のようにテーブルで表示されます。
> ffprobe -v quiet -show_format -show_streams -print_format json /path/to/movie.mp4 | from json
╭─────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │ ╭───┬────────────┬───────────────────────────────────────────┬─────────┬────────────┬──────────────────┬──────────┬─────╮ │
│ streams │ │ # │ codec_name │ codec_long_name │ profile │ codec_type │ codec_tag_string │ codec_ta │ ... │ │
│ │ │ │ │ │ │ │ │ g │ │ │
│ │ ├───┼────────────┼───────────────────────────────────────────┼─────────┼────────────┼──────────────────┼──────────┼─────┤ │
│ │ │ 0 │ h264 │ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 │ High │ video │ avc1 │ 0x316376 │ ... │ │
│ │ │ │ │ │ │ │ │ 61 │ │ │
│ │ │ 1 │ aac │ AAC (Advanced Audio Coding) │ LC │ audio │ mp4a │ 0x613470 │ ... │ │
│ │ │ │ │ │ │ │ │ 6d │ │ │
│ │ ╰───┴────────────┴───────────────────────────────────────────┴─────────┴────────────┴──────────────────┴──────────┴─────╯ │
│ │ ╭──────────────────┬───────────────────────────────────────────────────────────────────────────╮ │
│ format │ │ filename │ movie.mp4 │ │
│ │ │ format_name │ mov,mp4,m4a,3gp,3g2,mj2 │ │
│ │ │ format_long_name │ QuickTime / MOV │ │
│ │ │ start_time │ 0.000000 │ │
│ │ │ duration │ 101.358345 │ │
│ │ │ size │ 217176161 │ │
│ │ │ bit_rate │ 17141255 │ │
│ │ ╰──────────────────┴───────────────────────────────────────────────────────────────────────────╯ │
╰─────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
ビデオストリーム(streams の #0)の詳細を確認したいのであれば、パイプラインでget streams.0をつなげます。
❯ ffprobe -v quiet -show_format -show_streams -print_format json /path/to/movie.mp4 | from json | get streams.0
╭─────────────────────┬────────────────────────────────────────────────────────────────────────────────────╮
│ index │ 0 │
│ codec_name │ h264 │
│ codec_long_name │ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 │
│ profile │ High │
│ codec_type │ video │
│ codec_tag_string │ avc1 │
│ codec_tag │ 0x31637661 │
│ width │ 1920 │
│ height │ 1080 │
│ coded_width │ 1920 │
│ coded_height │ 1080 │
│ has_b_frames │ 1 │
│ pix_fmt │ yuv420p │
# 以下諸略
nushell で関数定義
動画の情報を取得するのにffprobe | from jsonの一連のコマンドを毎回書くのは面倒なので、nushell の関数として定義しておきます。関数定義は Windows 環境であれば~\Appdata\Roaming\nushell\config.nuに書きます。config nuコマンドでも上記ファイルを開けますが、その前にデフォルトのエディタを指定する必要があります。下記を nushell 上で実行するか、config.nuファイルに書き込んでおいてください。
$env.config.buffer_editor = "hx"
# vim, notepad などお好きなエディタを指定
関数定義はdef 関数名 [引数名1: 型, 引数名2: 型, ...] { /* 処理 */ }という風に書きます。今回の例では下記のようになります。
def video [arg: string] {
ffprobe -v quiet -show_format -show_streams -print_format json $arg | from json
}
nushell を再起動して下記コマンドを実行できるか確認しましょう。
> video /path/to/movie.mp4
特定のファイルの再生時間を取得するなら下記のようになります。
> video /path/to/movie.mp4 | get format.duration
101.358345
XX min XX sec XX ms のようにヒューマンリーダブルな表示にしたいですか?その場合は下記のようになります。
> video /path/to/movie.mp4 | get format.duration | into duration --unit sec
1min 41sec 358ms 345µs
再生時間を取得する関数もconfig.nuで定義しておきます。
def video-duration [arg: string] {
video $arg | get format.duration | into duration --unit sec
}
これでvideo-duration関数を実行すれば特定の動画の再生時間が取得できます。
> video-duration /path/to/movie.mp4
1min 41sec 358ms 345µs
フォルダ内のファイルの列挙
nushell にもlsコマンドがありますが、POSIX 互換ではなく構造化されたテーブルとして表示されます。
> ls
╭───┬────────────┬──────┬──────────┬─────────────╮
│ # │ name │ type │ size │ modified │
├───┼────────────┼──────┼──────────┼─────────────┤
│ 0 │ movie1.mp4 │ file │ 3.0 GB │ 6 years ago │
│ 1 │ movie2.mp4 │ file │ 4.2 GB │ 6 years ago │
│ 2 │ movie3.mp4 │ file │ 4.2 GB │ 6 years ago │
│ 3 │ movie4.mp4 │ file │ 615.1 MB │ 6 years ago │
│ 4 │ movie5.mp4 │ file │ 14.5 MB │ 6 years ago │
│ 5 │ movie6.mp4 │ file │ 690.0 MB │ 6 years ago │
╰───┴────────────┴──────┴──────────┴─────────────╯
ファイル名だけを取得したいならget nameをパイプラインでつなぎます。
> ls | get name
╭───┬────────────╮
│ 0 │ movie1.mp4 │
│ 1 │ movie2.mp4 │
│ 2 │ movie3.mp4 │
│ 3 │ movie4.mp4 │
│ 4 │ movie5.mp4 │
│ 5 │ movie6.mp4 │
╰───┴────────────╯
再生時間の合計を計算する
各動画ファイルの再生時間を取得してみましょう。eachコマンドを使います。
> ls | get name | each {|n| video-duration $n}
╭───┬─────────────────────────╮
│ 0 │ 34min 12sec 608ms │
│ 1 │ 47min 39sec 373ms 178µs │
│ 2 │ 47min 40sec 690ms 678µs │
│ 3 │ 6min 50sec 784ms │
│ 4 │ 9sec 568ms │
│ 5 │ 7min 39sec 520ms │
╰───┴─────────────────────────╯
describeコマンドを使うとデータの型を表示することができます。
> ls | get name | each {|n| video-duration $n} | describe
list<duration> (stream)
list<duration>型はmath sumコマンドのインプットに渡せるようですね。
> ls | get name | each {|n| video-duration $n} | math sum
2hr 24min 12sec 543ms 856µs
これで再生時間の合計が計算できました!
まとめ
nushell は便利なのでちょっと触ってみてはいかがでしょうか ![]()
config.nu の設定例
$env.config.buffer_editor = "hx"
$env.config.show_banner = false
def video [arg: string] {
ffprobe -v quiet -print_format json -show_format -show_streams $arg | from json
}
def video-duration [arg: string] {
video $arg | get format.duration | into duration --unit sec
}