Open26

ffmpeg でよく利用するコマンド群

PINTOPINTO
  • 指定フレーム番号から指定フレーム番号までの範囲を抽出・切り出す
ffmpeg \
-i in.mp4 \
-vf trim=start_frame=1168:end_frame=1178,setpts=PTS-STARTPTS \
-an \
out.mp4
PINTOPINTO
  • MP4 の Twitter対応MP4変換
ffmpeg \
-i output.mp4 \
-pix_fmt yuv420p \
-vcodec h264_nvenc \
output_.mp4
PINTOPINTO
  • GIF to MP4
ffmpeg \
-i demo.gif \
-movflags faststart \
-pix_fmt yuv420p \
-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" \
-q:v 1 \
demo.mp4
PINTOPINTO
  • mp4 アスペクト比を維持したサイズ変更
ffmpeg \
-i in.mp4 \
-vf scale=1280:-1 \
-vcodec mpeg4 \
-q:v 1 \
out.mp4

又は

ffmpeg \
-i in.mp4 \
-vf scale=1280:-1 \
-vcodec h264_nvenc \
out.mp4
PINTOPINTO
  • 動画から連番画像生成
ffmpeg -i 01.avi -vcodec png %06d.png
ffmpeg -i 01.avi -vcodec mjpeg %06d.jpg

ffmpeg -i input.mp4 -vf "fps=5" frameE_%06d.png
PINTOPINTO
  • 640x360 の動画に縦方向に16px足して、余白をグレーにし、表示位置を6px下にずらして中央寄せ
  • 生成される動画は 640x480
ffmpeg \
-i input_720x1280.mp4 \
-vf pad=h=ih+16:y=6:color=7d7d7d \
-q:v 1 \
output_736x1280.mp4
  • 横128px足して、縦 24px足して、表示位置を横64px目、縦12px目にずらして中央寄せ
ffmpeg \
-i 360x640.mp4 \
-vf pad=w=iw+128:h=ih+24:x=64:y=12:color=7d7d7d \
-q:v 1 \
384x768.mp4
  • 640x360 の動画に対して縦方向に120px足して、余白を黒にし、表示位置を60px下にずらして中央寄せ
  • 生成される動画は 640x480
ffmpeg \
-i test10_360x640.mp4 \
-vf pad=h=ih+120:y=60:color=000000 \
-q:v 1 \
test10_480x640.mp4
PINTOPINTO
  • 2枚の動画を水平方向に結合
ffmpeg \
-i left.mp4 \
-i right.mp4 \
-filter_complex "hstack" \
-vcodec h264_nvenc \
output_merge.mp4
  • 2枚の動画を垂直方向に結合
ffmpeg \
-i left.mp4 \
-i right.mp4 \
-filter_complex "vstack" \
-vcodec h264_nvenc \
output_merge.mp4
PINTOPINTO
  • 指定時間範囲でMP4をカット, 0秒から47秒をカット
ffmpeg \
-ss 00:00:00 \
-to 00:00:47 \
-i input.mp4 \
-c copy \
output.mp4
PINTOPINTO
  • ハードウェアアクセラレーションを使用しつつ webm to mp4 無劣化変換
ffmpeg -y -hwaccel cuda -i xxx.webm -vcodec copy xxx.mp4
PINTOPINTO
  • ハードウェアアクセラレーションを使用しつつ webm to mp4 無劣化変換 + フォルダ内サブフォルダ含めた再帰一括処理 (Ubuntu)
convert.sh
#!/bin/sh
for FILE in `find . -name "*.webm"`
do
    FILENAME=`echo ${FILE} | sed 's/\.[^\.]*$//'`
    ffmpeg \
    -y \
    -hwaccel cuda \
    -i ${FILENAME}.webm \
    -vcodec copy \
    -loglevel error \
    ${FILENAME}.mp4
done
PINTOPINTO
  • mp4 のフレームレートを無劣化で変更するスクリプト, 0.5倍速の例
ffmpeg -y \
-i 20231220073655.mp4 \
-af "atempo=0.5" \
-bsf:v setts=ts=TS*2.0 \
-c:v copy \
20231220073655_.mp4
  • mp4 のフレームレートを無劣化で一括変更するスクリプト + サブフォルダを含めた再帰一括処理 (Ubuntu), 0.66倍速の例
convert.sh
#!/bin/sh
for FILE in `find . -name "*.mp4"`
do
    FILENAME=`echo ${FILE} | sed 's/\.[^\.]*$//'`
    ffmpeg -y -i ${FILENAME}.mp4 -af "atempo=0.66" -bsf:v setts=ts=TS*1.5 -c:v copy ${FILENAME}_30.mp4
done
PINTOPINTO
  • MP4 の動画長 (時:分:秒) を調べる (HH:MM:SS 形式)
FILE="2023-0712-1824-08_xxxx.mp4"

ffmpeg -i $FILE 2>&1 | grep Duration | awk '{print $2}' | tr -d ,

00:05:32.69
  • MP4 の動画長 (時:分:秒) を調べる (秒換算形式)
FILE="2023-0712-1824-08_xxxx.mp4"

echo $(ffmpeg -i "$FILE" 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F: '{ if (NF==3) { print ($1*3600)+($2*60)+$3 } else { print ($1*60)+$2 } }')

332.69
PINTOPINTO
  • MP4 の生成日時、動画長、最終更新日時を取得する
get_end_time.sh
#!/bin/bash

# MP4 ファイルのパスを引数として受け取る
FILE="$1"

# creation_time を取得
CREATION_TIME=$(ffmpeg -i "$FILE" 2>&1 | grep creation_time | head -1 | awk -F 'creation_time=' '{print $2}' | cut -d' ' -f1)

# ファイルの持つ duration(秒)を取得
DURATION=$(ffmpeg -i "$FILE" 2>&1 | grep Duration | awk '{print $2}' | tr -d , | awk -F: '{ if (NF==3) { print ($1*3600)+($2*60)+$3 } else { print ($1*60)+$2 } }')

# 最終の録画時刻を算出(CREATION_TIME から DURATION を加算)
END_TIME=$(date -d "$CREATION_TIME + $DURATION seconds" +"%Y-%m-%d %H:%M:%S")

echo "Creation Time: $CREATION_TIME"
echo "Duration: $DURATION seconds"
echo "End Time: $END_TIME"
sudo chmod +x get_end_time.sh
./get_end_time.sh "xxxx.mp4"

Creation Time: 
Duration: 135.66 seconds
End Time: 2023-08-19 12:10:17
PINTOPINTO
  • MP4 ファイル名が録画終了日時を表している場合に、ファイル名から正規表現を使用して録画終了時分秒を抽出する
  • BASH_REMATCH は正規表現にマッチした文字列を参照する

https://qiita.com/YumaInaura/items/b0f29b0061779e296068

  • サンプルのファイル名 : 2023-0712-1824-08_xyz100-12.mp4
#!/bin/bash

# MP4 ファイルのパスを引数として受け取る
FILE="$1"

# 正規表現を使用してファイル名を分解する
if [[ $FILE =~ ([0-9]{2})([0-9]{2})-([0-9]{2})_xyz ]]; then
    HOUR="${BASH_REMATCH[1]}"
    MINUTE="${BASH_REMATCH[2]}"
    SECOND="${BASH_REMATCH[3]}"
fi

echo "Hour: $HOUR"
echo "Minute: $MINUTE"
echo "Second: $SECOND"
  • 00時00分00秒からの経過秒に変換するパターン (#0 は変数の先頭のゼロを削除する)
#!/bin/bash

# MP4 ファイルのパスを引数として受け取る
FILE="$1"

# 正規表現を使用してファイル名を分解する
if [[ $FILE =~ ([0-9]{2})([0-9]{2})-([0-9]{2})_xyz ]]; then
    HOUR=${BASH_REMATCH[1]}
    MINUTE=${BASH_REMATCH[2]}
    SECOND=${BASH_REMATCH[3]}
fi

echo $((${HOUR#0}*3600+${MINUTE#0}*60+${SECOND#0}))
66248
PINTOPINTO
  • 時分秒ミリ秒をあらわす 00:02:15.66 からbashを使用して 0, 2, 15, 66 を抽出する方法 (#0 は変数の先頭のゼロを削除する)
TIME_STR="00:02:15.66"

if [[ $TIME_STR =~ ([0-9]{2}):([0-9]{2}):([0-9]{2})\.([0-9]{2}) ]]; then
    HOUR=${BASH_REMATCH[1]}
    MINUTE=${BASH_REMATCH[2]}
    SECOND=${BASH_REMATCH[3]}
    MILLISECOND=${BASH_REMATCH[4]}
fi

echo "Hours: ${HOUR#0}"
echo "Minutes: ${MINUTE#0}"
echo "Seconds: ${SECOND#0}"
echo "Milliseconds: ${MILLISECOND#0}"
Hours: 0
Minutes: 2
Seconds: 15
Milliseconds: 66
PINTOPINTO
  • 任意のフォルダのサブフォルダが日付のような MM_DD 形式で複数保存されている
  • MM_DD フォルダの配下には、あらかじめファイル名に日時分秒の情報が含まれていて、あらかじめ時系列にソートしやすい形式で複数の MP4 ファイルが保存されている
  • 上記の前提で、ディレクトリは順番通りに(変更しないで)、そのディレクトリ内のファイルを降順で処理するbashスクリプト

フォルダ例:

./07_12/0001.mp4
./07_12/0002.mp4
./07_12/0003.mp4
./07_12/0004.mp4
./07_12/0005.mp4
./07_13/0001.mp4
./07_13/0002.mp4
./07_13/0003.mp4
./07_13/0004.mp4
./07_13/0005.mp4

スクリプト例:

#!/bin/bash

# ディレクトリを取得(ディレクトリの順序はそのまま)
for dir in $(ls -d */ | grep '^[0-9][0-9]_[0-9][0-9]/$'); do
    # ディレクトリ内のファイルを降順で取得して処理
    for file in $(ls -r ./$dir*.mp4); do
        # ここでファイルに対して何らかの処理を行います。
        echo "Processing $file"

        # 例: ファイルの内容を表示
        # cat $file

        # 他の処理が必要な場合は上記の処理を置き換えまたは追加してください。
    done
done
PINTOPINTO
  • 秒数を HH:MM:SS.mmm 形式に変換する方法
#!/bin/bash

# 428秒150ミリ秒
SECONDS=428.150

# 整数部分の秒数を取得
INT_SECONDS=${SECONDS%.*}

# ミリ秒を取得
MILLISECONDS=$(printf "%03d" $(echo "$SECONDS" | awk -F. '{if ($2) print $2; else print "0"}'))

HOURS=$((INT_SECONDS / 3600))
MINUTES=$(( (INT_SECONDS % 3600) / 60 ))
SECONDS=$((INT_SECONDS % 60))

printf "%02d:%02d:%02d.%s\n" $HOURS $MINUTES $SECONDS $MILLISECONDS
PINTOPINTO
  • 動画ファイルの末尾に HH:MM:SS.mmm 分のブランク映像をパディングする
  1. 無音音声ストリームを生成する: これは、動画に音声が含まれている場合に、ブランク映像の間も音声が必要となるためです。
  2. ブランクの映像フィルタを使用してブランクの映像を生成する。
  3. 元の動画とブランクの映像および音声を結合する。
# 処理対象のファイル
FILE="xxxx.mp4"
# パディングする HH:MM:SS.mmm
PADDING_TIME=00:00:02.500

DIRPATH=$(dirname ${FILE})
FILENAME=$(basename ${FILE})

# 元動画の情報取得
WIDTH=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "${FILE}")
HEIGHT=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "${FILE}")
# FRAME_RATE=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 "${FILE}")
# DURATION=$(ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "${FILE}")
TIMEBASE=$(ffprobe -v error -select_streams v:0 -show_entries stream=time_base -of default=noprint_wrappers=1:nokey=1 "${FILE}" | awk -F'/' '{print $2}')

# -q:v 0: 最高品質(しかし、ビットレートは最も高い)
# -q:v 23: デフォルトの品質
# -q:v 31: 最低品質(しかし、ビットレートは最も低い)

# ブランク映像の生成
ffmpeg \
-y \
-f lavfi \
-i "color=black:r=50/3:s=${WIDTH}x${HEIGHT}" \
-f lavfi \
-i "anullsrc=r=44100:cl=stereo" \
-t ${PADDING_DULATION_HHMMSSMMM} blank_video.mp4

# 元動画とブランク映像をマージ
ffmpeg \
-y \
-i ${FILE} \
-i blank_video.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[vtemp][aout];[vtemp]fps=50/3[vout]" \
-q:v 20 \
-map "[vout]" \
-map "[aout]" \
-video_track_timescale ${TIMEBASE} \
${DIRPATH}/pad_${FILENAME}

rm blank_video.mp4
PINTOPINTO
  • ffmpeg のログを非表示にする
  1. ログの出力レベルを変更する:
    ffmpeg には -loglevel オプションがあり、これを使用してログの詳細度を制御できます。quiet モードに設定することで、ほとんどのメッセージを非表示にできます。
    ffmpeg -i input.mp4 -loglevel quiet output.avi
    
  2. 標準エラー出力を無視する:
    すべてのログメッセージを完全に無視したい場合は、bash で標準エラー出力を /dev/null にリダイレクトすることで、エラーメッセージを非表示にすることができます。
    ffmpeg -i input.mp4 output.avi 2>/dev/null
    
PINTOPINTO
  • MM_DD という規則で月日ごとに作成された複数のフォルダの配下にある .mp4 ファイルは基本的に10分間隔で連続して保存されている
  • しかし、録画が失敗している場合もあり、動画長が10分ちょうどになっていなかったり、前後の動画が連続していないときがある
  • 上記条件において、各フォルダ内の最新の日付の .mp4 から逆順に処理をしていき、撮影時間が欠落している部分をブランクの動画を自動生成してパディングする
#!/bin/bash

GREEN="\033[32m"
RESET="\033[0m"
# パディング後に生成される動画の品質設定
# -q:v 0: 最高品質(しかし、ビットレートは最も高い)
# -q:v 23: デフォルトの品質
# -q:v 31: 最低品質(しかし、ビットレートは最も低い)
MOVIE_QUALITY=20
FRAME_RATE="50/3"

# ディレクトリを取得(ディレクトリの順序はそのまま)
for FOLDER in $(ls -d */ | grep '^[0-9][0-9]_[0-9][0-9]/$'); do

    # パディング算出用変数の初期化
    TMP_PREV_ESTIMATED_TIME=0

    # ディレクトリ内のファイルを降順で取得して処理
    for FILE in $(ls -r ./${FOLDER}*.mp4); do
        # 動画終了時刻(秒) の取得 ##########################################################
        if [[ ${FILE} =~ ([0-9]{2})([0-9]{2})-([0-9]{2})_ ]]; then
            HOUR=${BASH_REMATCH[1]}
            MINUTE=${BASH_REMATCH[2]}
            SECOND=${BASH_REMATCH[3]}
        fi
        MOVIE_END_TIME=$((${HOUR#0}*3600+${MINUTE#0}*60+${SECOND#0}))
        ################################################################################

        # 動画長(秒) の取得 ##############################################################
        MOVIE_DULATION=$(ffmpeg -i "$FILE" 2>&1 | grep Duration | \
            awk '{print $2}' | tr -d , | \
            awk -F: '{ if (NF==3) { print ($1*3600)+($2*60)+$3 } else { print ($1*60)+$2 } }')

        # 現在動画の末尾にパディングすべき時間幅秒の算出 #######################################
        # TMP_PREV_ESTIMATED_TIME > 0 なおかつ MOVIE_END_TIME < TMP_PREV_ESTIMATED_TIME のときのみ計算
        # パディングすべき秒 = (ひとつ前の動画から推定した終了時刻秒 - 今の動画の終了時刻秒)
        PADDING_DULATION=0.000
        if [ "$(echo "${TMP_PREV_ESTIMATED_TIME} > 0" | bc -l)" -eq 1 ] && [ "$(echo "${MOVIE_END_TIME} < ${TMP_PREV_ESTIMATED_TIME}" | bc -l)" -eq 1 ]; then
            PADDING_DULATION_CALCULATED=$(echo ${TMP_PREV_ESTIMATED_TIME} - ${MOVIE_END_TIME} | bc)
            PADDING_DULATION=$(printf "%.3f" ${PADDING_DULATION_CALCULATED})
        fi
        ################################################################################

        # 現在動画ファイルの dulation と 動画終了時刻 を基準にして算出した1つ前の動画のあるべき撮影終了時刻を計算
        PREV_ESTIMATED_TIME_CALCULATED=$(echo ${MOVIE_END_TIME} - ${MOVIE_DULATION} | bc)
        PREV_ESTIMATED_TIME=$(printf "%.3f" ${PREV_ESTIMATED_TIME_CALCULATED})
        printf "${GREEN}FILE: ${FILE} MOVIE_DULATION: ${MOVIE_DULATION}, MOVIE_END_TIME: ${MOVIE_END_TIME}, PREV_ESTIMATED_TIME: ${PREV_ESTIMATED_TIME}, PADDING_DULATION: ${PADDING_DULATION}${RESET}\n"
        TMP_PREV_ESTIMATED_TIME=${PREV_ESTIMATED_TIME}
        ################################################################################

        # 動画の末尾にパディング ###########################################################
        # PADDING_DULATION > 0.000 のときのみ
        # PADDING_DULATION = 0.000 のときはファイル名の先頭に pad_ と付与してファイルコピーするだけ
        DIRPATH=$(dirname ${FILE})
        FILENAME=$(basename ${FILE})
        if [ "$(echo "${PADDING_DULATION} > 0.000" | bc -l)" -eq 1 ]; then
            # PADDING_DULATION (秒表現) を HH:MM:SS.mmm 形式に変換する
            # 整数部分の秒数を取得
            INT_SECONDS=${PADDING_DULATION%.*}
            # ミリ秒を取得
            HOURS=$((INT_SECONDS / 3600))
            MINUTES=$(( (INT_SECONDS % 3600) / 60 ))
            SECONDS=$((INT_SECONDS % 60))
            MILLISECONDS=$(echo ${PADDING_DULATION} | cut -d "." -f 2)
            PADDING_DULATION_HHMMSSMMM=$(printf "%02d:%02d:%02d.%s" ${HOURS} ${MINUTES} ${SECONDS} ${MILLISECONDS})
            # 動画の末尾に PADDING_DULATION_HHMMSSMMM 分のブランク映像を追加する
            # 元動画の情報取得
            WIDTH=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of default=noprint_wrappers=1:nokey=1 "${FILE}")
            HEIGHT=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "${FILE}")
            # FRAME_RATE=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 "${FILE}") # 正確な値が取得できないときがある
            TIMEBASE=$(ffprobe -v error -select_streams v:0 -show_entries stream=time_base -of default=noprint_wrappers=1:nokey=1 "${FILE}" | awk -F'/' '{print $2}')

            # -q:v 0: 最高品質(しかし、ビットレートは最も高い)
            # -q:v 23: デフォルトの品質
            # -q:v 31: 最低品質(しかし、ビットレートは最も低い)

            # ブランク映像の生成
            printf "${GREEN}@@@@@@@@@@@@@@@@@@@ Make blank video FRAME_RATE: ${FRAME_RATE}, WIDTHxHEIGHT: ${WIDTH}x${HEIGHT}, PADDING_DULATION_HHMMSSMMM: ${PADDING_DULATION_HHMMSSMMM}, TIMEBASE: ${TIMEBASE}${RESET}\n"
            ffmpeg \
            -y \
            -f lavfi \
            -i "color=black:r=${FRAME_RATE}:s=${WIDTH}x${HEIGHT}" \
            -f lavfi \
            -i "anullsrc=r=44100:cl=stereo" \
            -loglevel error \
            -t ${PADDING_DULATION_HHMMSSMMM} blank_video.mp4

            # 元動画とブランク映像をマージ
            printf "${GREEN}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Videos merge${RESET}\n"
            ffmpeg \
            -y \
            -i ${FILE} \
            -i blank_video.mp4 \
            -filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[vtemp][aout];[vtemp]fps=${FRAME_RATE}[vout]" \
            -q:v ${MOVIE_QUALITY} \
            -map "[vout]" \
            -map "[aout]" \
            -video_track_timescale ${TIMEBASE} \
            -loglevel error \
            ${DIRPATH}/pad_${FILENAME}

            rm blank_video.mp4
        else
            printf "${GREEN}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Videos file copy${RESET}\n"
            cp ${FILE} ${DIRPATH}/pad_${FILENAME}
        fi
        ################################################################################
    done
done

PINTOPINTO

MP4動画を 10FPS のレートで .png に変換するコマンド

ffmpeg -i your_video.mp4 -vf "fps=1.5,scale=672:384" frame_%05d.png
rename 's/frame_0/frame_1/' frame_0*.png
rename 's/frame_0/frame_2/' frame_0*.png
PINTOPINTO

6桁の連番がファイル名になった .jpg ファイルを 30 FPS で MP4 に変換するコマンド

ffmpeg \
-framerate 30 \
-i %06d.jpg \
-c:v mpeg4 \
-pix_fmt yuv420p \
-q:v 0 \
-vf "scale=trunc(iw/2):trunc(ih/2)" \
-vcodec h264_nvenc \
output.mp4