オンデマンド講義用の動画変換スクリプト

投稿者: | 2020年4月18日

1-2ターム(前期)の講義は、コロナの影響でオンデマンドの動画講義となりました。講義は5月からスタートしますが、講義用の動画を準備せねばなりません。

ipad上でGood Note使ってPDFファイルにペンで書き込みながら講義することが多いので、ipadの画面と音声をそのまま録画するつもりです。板書メインの講義も、Good Noteに書き込みながら音声付きで録画する予定です。

パソコンの画面をそのまま録画すると、サイズが無駄に大きくなりすぎます。そこでPythonでffmepgを呼び出して動画ファイルを一括で変換するスクリプトを作りました。引数で与えた動画ファイルをdefaultで480pに変換します。変換すると、元の動画ファイルのcreation_timeが消えてしまうので、creation_timeを保存するようffmpegで変換先の動画ファイルに書き込みます。元々、creation_timeがない場合もあるので、その時はファイルスタンプを取得して、creation_timeにすることとしました。

#!/usr/bin/env python
import argparse
import datetime as dt
import os
import re
import subprocess as sp

# パソコン上でのYouTubeの標準アスペクト比は16:9
# https://support.google.com/youtube/answer/6375112?co=GENIE.Platform%3DDesktop&hl=ja
# 2160p: 3840x2160 [4K]
# 1440p: 2560x1440 [2K]
# 1080p: 1920x1080 [full-HD]
# 720p: 1280x720   [HDTV]
# 480p: 854x480
# 360p: 640x360
# 240p: 426x240

par = argparse.ArgumentParser(description="mp4 converter through ffmpeg")
par.add_argument('dtsfiles', nargs='+', help="movie files")
par.add_argument('-s', '--height', choices=[240, 360, 480, 720, 1080, 1440],
                 default=480, type=int, help="video height [720]")
par.add_argument('-v', '--verbose', default=False, action='store_true',
                 help="verbose [False]")
args = par.parse_args()


com = """
ffmpeg -y -i {INPUTFILE}
  -c:v libx264 -crf 20 -vf scale={SCALE}
  -metadata creation_time={DATE}
  {OUTPUTFILE}
"""

for f in args.dtsfiles:
    p = sp.run(['ffprobe', f], stdout=sp.PIPE, stderr=sp.PIPE)
    o = re.search("Stream.+ (\d+)x(\d+),.+\n", p.stderr.decode())
    scale = "-2:{}".format(args.height)
    obj = re.search("creation_time +: +(.+)\n", p.stderr.decode())
    if obj is not None:
        date = obj.group(1)
    else:
        date = dt.datetime.fromtimestamp(os.stat(f).st_mtime)
        date = date.strftime('%Y-%m-%dT%H:%M:%S')
    basename, ext = os.path.splitext(f)
    if ext == ".mp4":
        ext = "_new.mp4"
    else:
        ext = ".mp4"
    outfile = basename + ext
    c = com.format(INPUTFILE=f, DATE=date, OUTPUTFILE=outfile, SCALE=scale)
    print("{}({}) -> {}({})...".format(f, scale, outfile, scale))
    if args.verbose:
        sp.run(c.split())
    else:
        sp.run(c.split(), stdout=sp.PIPE, stderr=sp.PIPE)